Skip to content

Commit 7fbe2e2

Browse files
committed
Taking care of some slow solutions
1 parent 3488b51 commit 7fbe2e2

14 files changed

Lines changed: 799 additions & 642 deletions

File tree

lib/2016/day_12.ex

Lines changed: 99 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,76 +3,128 @@ defmodule AdventOfCode.Y2016.Day12 do
33
--- Day 12: Leonardo's Monorail ---
44
Problem Link: https://adventofcode.com/2016/day/12
55
Difficulty: s
6-
Tags: slow op-code
6+
Tags: assembled-optimization assembunny
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
99

1010
def input, do: InputReader.read_from_file(2016, 12)
1111

12-
@tokens ~w/a b c d cpy inc dec jnz/
13-
@regs %{"a" => 0, "b" => 0, "c" => 0, "d" => 0}
14-
1512
def run(input \\ input()) do
1613
instructions = parse(input)
14+
instr_tuple = List.to_tuple(instructions)
15+
size = tuple_size(instr_tuple)
1716

18-
solution_1 = Task.async(fn -> run_1(instructions) end)
19-
solution_2 = Task.async(fn -> run_2(instructions) end)
20-
21-
{
22-
Task.await(solution_1, :infinity),
23-
Task.await(solution_2, :infinity)
24-
}
25-
end
26-
27-
def run_1(instructions) do
28-
Map.get(exec(instructions, @regs, 0, Enum.count(instructions)), "a")
29-
end
17+
task_1 = Task.async(fn -> solve(instr_tuple, {0, 0, 0, 0}, 0, size) end)
18+
task_2 = Task.async(fn -> solve(instr_tuple, {0, 0, 1, 0}, 0, size) end)
3019

31-
def run_2(instructions) do
32-
Map.get(exec(instructions, %{@regs | "c" => 1}, 0, Enum.count(instructions)), "a")
20+
{Task.await(task_1, :infinity), Task.await(task_2, :infinity)}
3321
end
3422

3523
def parse(data) do
3624
data
3725
|> Transformers.lines()
38-
|> Enum.map(fn instruction ->
39-
instruction
40-
|> String.split(" ")
41-
|> Enum.map(&sanitize/1)
26+
|> Enum.map(fn line ->
27+
String.split(line, " ") |> Enum.map(&sanitize/1)
4228
end)
43-
|> Enum.with_index()
44-
|> Map.new(fn {cmd, idx} -> {idx, cmd} end)
4529
end
4630

47-
defp exec(_, regs, s, s), do: regs
31+
# Use atoms for registers to distinguish from constant integers
32+
defp sanitize(val) do
33+
case val do
34+
"a" ->
35+
:a
4836

49-
defp exec(cmds, regs, idx, s) do
50-
case cmds[idx] do
51-
["cpy", val, reg] when is_integer(val) ->
52-
exec(cmds, %{regs | reg => val}, idx + 1, s)
37+
"b" ->
38+
:b
5339

54-
["cpy", reg_val, reg] ->
55-
exec(cmds, %{regs | reg => regs[reg_val]}, idx + 1, s)
40+
"c" ->
41+
:c
5642

57-
["inc", reg] ->
58-
exec(cmds, %{regs | reg => regs[reg] + 1}, idx + 1, s)
43+
"d" ->
44+
:d
5945

60-
["dec", reg] ->
61-
exec(cmds, %{regs | reg => regs[reg] - 1}, idx + 1, s)
62-
63-
["jnz", 0, _] ->
64-
exec(cmds, regs, idx + 1, s)
65-
66-
["jnz", val, step] when is_integer(val) ->
67-
exec(cmds, regs, idx + step, s)
68-
69-
["jnz", reg_val, step] ->
70-
exec(cmds, regs, jump_on_nz(regs[reg_val], idx, step), s)
46+
other ->
47+
case Integer.parse(other) do
48+
{n, ""} -> n
49+
_ -> other
50+
end
7151
end
7252
end
7353

74-
defp jump_on_nz(val, idx, step), do: idx + ((val == 0 && 1) || step)
54+
defp solve(_, {a, _, _, _}, pc, size) when pc >= size, do: a
55+
56+
defp solve(cmds, regs, pc, size) do
57+
case elem(cmds, pc) do
58+
["inc", r1] ->
59+
# Loop Optimization: Add R2 to R1 and zero out R2
60+
if pc + 2 < size do
61+
case elem(cmds, pc + 1) do
62+
["dec", r2] ->
63+
case elem(cmds, pc + 2) do
64+
["jnz", ^r2, -2] ->
65+
val = get_reg(regs, r1) + get_reg(regs, r2)
66+
solve(cmds, put_reg(regs, r1, val) |> put_reg(r2, 0), pc + 3, size)
67+
68+
_ ->
69+
val = get_reg(regs, r1) + 1
70+
solve(cmds, put_reg(regs, r1, val), pc + 1, size)
71+
end
72+
73+
_ ->
74+
val = get_reg(regs, r1) + 1
75+
solve(cmds, put_reg(regs, r1, val), pc + 1, size)
76+
end
77+
else
78+
val = get_reg(regs, r1) + 1
79+
solve(cmds, put_reg(regs, r1, val), pc + 1, size)
80+
end
81+
82+
["dec", r1] ->
83+
# Loop Optimization: dec r1; inc r2; jnz r1 -2
84+
if pc + 2 < size do
85+
case elem(cmds, pc + 1) do
86+
["inc", r2] ->
87+
case elem(cmds, pc + 2) do
88+
["jnz", ^r1, -2] ->
89+
val = get_reg(regs, r2) + get_reg(regs, r1)
90+
solve(cmds, put_reg(regs, r2, val) |> put_reg(r1, 0), pc + 3, size)
91+
92+
_ ->
93+
val = get_reg(regs, r1) - 1
94+
solve(cmds, put_reg(regs, r1, val), pc + 1, size)
95+
end
96+
97+
_ ->
98+
val = get_reg(regs, r1) - 1
99+
solve(cmds, put_reg(regs, r1, val), pc + 1, size)
100+
end
101+
else
102+
val = get_reg(regs, r1) - 1
103+
solve(cmds, put_reg(regs, r1, val), pc + 1, size)
104+
end
105+
106+
["cpy", src, target] ->
107+
val = get_reg(regs, src)
108+
solve(cmds, put_reg(regs, target, val), pc + 1, size)
109+
110+
["jnz", src, step] ->
111+
val = get_reg(regs, src)
112+
new_pc = if val != 0, do: pc + step, else: pc + 1
113+
solve(cmds, regs, new_pc, size)
114+
end
115+
end
75116

76-
defp sanitize(reg) when reg in @tokens, do: reg
77-
defp sanitize(reg), do: String.to_integer(reg)
117+
# Helper functions for O(1) register access using pattern matching
118+
@compile {:inline, get_reg: 2, put_reg: 3}
119+
defp get_reg({a, _, _, _}, :a), do: a
120+
defp get_reg({_, b, _, _}, :b), do: b
121+
defp get_reg({_, _, c, _}, :c), do: c
122+
defp get_reg({_, _, _, d}, :d), do: d
123+
# It's a constant integer
124+
defp get_reg(_, val), do: val
125+
126+
defp put_reg({_, b, c, d}, :a, v), do: {v, b, c, d}
127+
defp put_reg({a, _, c, d}, :b, v), do: {a, v, c, d}
128+
defp put_reg({a, b, _, d}, :c, v), do: {a, b, v, d}
129+
defp put_reg({a, b, c, _}, :d, v), do: {a, b, c, v}
78130
end

lib/2017/day_13.ex

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ defmodule AdventOfCode.Y2017.Day13 do
33
--- Day 13: Packet Scanners ---
44
Problem Link: https://adventofcode.com/2017/day/13
55
Difficulty: s
6-
Tags: sequence slow
6+
Tags: sequence optimization
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
99

1010
def input, do: InputReader.read_from_file(2017, 13)
1111

1212
def run(input \\ input()) do
13-
input = parse(input)
14-
limit = Enum.max(Map.keys(input))
13+
# Optimization: Store scanners as a list of {pos, period} tuples
14+
scanners =
15+
input
16+
|> parse()
17+
|> Enum.map(fn {pos, range} -> {pos, 2 * (range - 1), range} end)
1518

16-
task_1 = Task.async(fn -> run_1(input, limit) end)
17-
task_2 = Task.async(fn -> run_2(input, limit) end)
19+
# Scanners with smaller periods are more likely to catch us early,
20+
# so sorting them can speed up Enum.any? short-circuiting in Part 2.
21+
sorted_scanners = Enum.sort_by(scanners, fn {_, period, _} -> period end)
22+
23+
task_1 = Task.async(fn -> solve_1(scanners) end)
24+
task_2 = Task.async(fn -> solve_2(sorted_scanners) end)
1825

1926
{Task.await(task_1, :infinity), Task.await(task_2, :infinity)}
2027
end
@@ -23,42 +30,23 @@ defmodule AdventOfCode.Y2017.Day13 do
2330
data
2431
|> Transformers.lines()
2532
|> Map.new(fn line ->
26-
line
27-
|> String.split(": ")
28-
|> Enum.map(&String.to_integer/1)
29-
|> List.to_tuple()
33+
[pos, range] = String.split(line, ": ") |> Enum.map(&String.to_integer/1)
34+
{pos, range}
3035
end)
3136
end
3237

33-
defp run_1(input, limit, delay \\ 0) do
34-
input
35-
|> sneak(limit, delay)
36-
|> severity(input)
37-
end
38-
39-
defp run_2(input, limit, delay \\ 0) do
40-
case sneak(input, limit, delay) do
41-
[] -> delay
42-
_ -> run_2(input, limit, delay + 1)
43-
end
38+
defp solve_1(scanners) do
39+
scanners
40+
|> Enum.filter(fn {pos, period, _} -> rem(pos, period) == 0 end)
41+
|> Enum.map(fn {pos, _, range} -> pos * range end)
42+
|> Enum.sum()
4443
end
4544

46-
defp sneak(input, limit, delay) do
47-
Enum.reduce(0..limit, [], fn pos, detections ->
48-
case Map.get(input, pos) do
49-
nil -> detections
50-
range -> (detect?(pos, range, delay) && [pos | detections]) || detections
51-
end
45+
defp solve_2(scanners) do
46+
Stream.iterate(0, &(&1 + 1))
47+
|> Enum.find(fn delay ->
48+
# Short-circuiting any check makes this much faster than iterating all layers
49+
!Enum.any?(scanners, fn {pos, period, _} -> rem(delay + pos, period) == 0 end)
5250
end)
5351
end
54-
55-
defp detect?(pico_second, range, delay) do
56-
rem(pico_second + delay, 2 * (range - 1)) == 0
57-
end
58-
59-
defp severity(detections, input) do
60-
input
61-
|> Map.take(detections)
62-
|> Enum.sum_by(fn {k, v} -> k * v end)
63-
end
6452
end

lib/2017/day_15.ex

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,72 @@ defmodule AdventOfCode.Y2017.Day15 do
33
--- Day 15: Dueling Generators ---
44
Problem Link: https://adventofcode.com/2017/day/15
55
Difficulty: l
6-
Tags: number-theory bitwise slow
7-
8-
FIXME: Number being Marsenne Prime, can't help but think there's a faster way to this.
6+
Tags: number-theory bitwise optimization
97
"""
108
import Bitwise
119

1210
alias AdventOfCode.Helpers.{InputReader, Transformers}
1311

1412
def input, do: InputReader.read_from_file(2017, 15)
1513

16-
@multipliers %{"A" => 16_807, "B" => 48_271}
14+
@mod 2_147_483_647
1715

1816
def run(input \\ input()) do
19-
input = parse(input)
17+
[a, b] = parse(input)
2018

21-
part_1 = Task.async(fn -> final_count_1(input) end)
22-
part_2 = Task.async(fn -> final_count_2(input) end)
19+
# Use Task.async for parallelism
20+
part_1 = Task.async(fn -> solve_1(a, b, 0, 0) end)
21+
part_2 = Task.async(fn -> solve_2(a, b, 0, 0) end)
2322

24-
{
25-
Task.await(part_1, :infinity),
26-
Task.await(part_2, :infinity)
27-
}
23+
{Task.await(part_1, :infinity), Task.await(part_2, :infinity)}
2824
end
2925

30-
@regex ~r"Generator (A|B) starts with (\d+)"
3126
def parse(input) do
3227
input
3328
|> Transformers.lines()
3429
|> Enum.map(fn line ->
35-
[name, start] = Regex.run(@regex, line, capture: :all_but_first)
36-
37-
{String.to_integer(start), @multipliers[name]}
30+
[_, val] = Regex.run(~r/starts with (\d+)/, line)
31+
String.to_integer(val)
3832
end)
3933
end
4034

41-
defp final_count_1([{a, multiplier_a}, {b, multiplier_b}]) do
42-
get_final_count(
43-
40_000_000,
44-
[generator(a, multiplier_a), generator(b, multiplier_b)]
45-
)
46-
end
35+
defp solve_1(_, _, 40_000_000, result), do: result
4736

48-
defp final_count_2([{a, multiplier_a}, {b, multiplier_b}]) do
49-
get_final_count(5_000_000, [
50-
generator(a, multiplier_a, multiple_of(4)),
51-
generator(b, multiplier_b, multiple_of(8))
52-
])
53-
end
37+
defp solve_1(a, b, count, result) do
38+
na = next(a, 16_807)
39+
nb = next(b, 48_271)
5440

55-
defp generator(initial, multiplier, filter \\ & &1) do
56-
initial
57-
|> next(multiplier)
58-
|> Stream.iterate(&next(&1, multiplier))
59-
|> filter.()
41+
if (na &&& 0xFFFF) == (nb &&& 0xFFFF) do
42+
solve_1(na, nb, count + 1, result + 1)
43+
else
44+
solve_1(na, nb, count + 1, result)
45+
end
6046
end
6147

62-
defp get_final_count(limit, [a, b]) do
63-
[a, b]
64-
|> Stream.zip()
65-
|> Enum.take(limit)
66-
|> Enum.count(&same_lowest_bits?/1)
48+
defp solve_2(_, _, 5_000_000, result), do: result
49+
50+
defp solve_2(a, b, count, result) do
51+
na = next_filtered(a, 16_807, 3)
52+
nb = next_filtered(b, 48_271, 7)
53+
54+
if (na &&& 0xFFFF) == (nb &&& 0xFFFF) do
55+
solve_2(na, nb, count + 1, result + 1)
56+
else
57+
solve_2(na, nb, count + 1, result)
58+
end
6759
end
6860

69-
@mask (1 <<< 16) - 1
70-
defp same_lowest_bits?({a, b}), do: bxor(a &&& @mask, b &&& @mask) == 0
61+
@compile {:inline, next: 2}
62+
defp next(val, mult) do
63+
prod = val * mult
64+
# Mersenne Prime (2^31 - 1) optimization
65+
res = (prod >>> 31) + (prod &&& @mod)
66+
if res >= @mod, do: res - @mod, else: res
67+
end
7168

72-
defp multiple_of(amount), do: &Stream.filter(&1, fn val -> rem(val, amount) == 0 end)
73-
defp next(prev, multiplier), do: rem(prev * multiplier, 2_147_483_647)
69+
defp next_filtered(val, mult, mask) do
70+
n = next(val, mult)
71+
# Using bitwise & mask for multiple_of(4) -> &&& 3 == 0, multiple_of(8) -> &&& 7 == 0
72+
if (n &&& mask) == 0, do: n, else: next_filtered(n, mult, mask)
73+
end
7474
end

0 commit comments

Comments
 (0)