d22
# Mix.install([
# {:memoize, "~> 1.4"}
# ])
Section
# Memoize.invalidate()
div(1, 2)
ExUnit.start()
defmodule D22 do
def parse(input) do
String.split(input, "\n", trim: true)
|> Enum.map(&String.to_integer/1)
end
defp mix(value, transform) do
rem(Bitwise.bxor(value, transform.(value)), 16_777_216)
end
def evolve(secret) do
secret
|> mix(&(&1 * 64))
|> mix(&div(&1, 32))
|> mix(&(&1 * 2048))
end
def generate(secret) do
Stream.iterate(secret, &D22.evolve/1)
|> Enum.take(2001)
end
def part1(secrets) do
Enum.map(secrets, fn secret -> List.last(generate(secret)) end)
|> Enum.sum()
end
def collate(secret) do
prices = generate(secret) |> Enum.map(&rem(&1, 10))
Enum.zip(prices, tl(prices))
|> Enum.map(fn {prev, curr} -> {curr - prev, curr} end)
end
def sell(trigger, book), do: sell(trigger, book, trigger)
defp sell(_, [], _), do: 0
defp sell(trigger, [{actual, price} | book], [expected | rest]) do
rest = if expected == actual, do: rest, else: trigger
if Enum.empty?(rest) do
price
else
sell(trigger, book, rest)
end
end
# Keying by an integer instead of a list cuts runtime by about half.
defp window_key([a, b, c, d]), do: (((a + 9) * 20 + (b + 9)) * 20 + (c + 9)) * 20 + (d + 9)
def window(book), do: window(book, [], %{})
defp window([{a, price} | book], [b, c, d | _], acc) do
changes = [a, b, c, d]
window(book, changes, Map.put_new(acc, window_key(changes), price))
end
defp window([], _, acc), do: acc
defp window([{head, _} | book], changes, acc), do: window(book, [head | changes], acc)
def part2(secrets) do
Enum.map(secrets, fn secret -> window(collate(secret)) end)
|> Enum.reduce(fn windowed, acc ->
Enum.reduce(windowed, acc, fn {key, price}, acc ->
Map.update(acc, key, price, &(&1 + price))
end)
end)
|> Map.values()
|> Enum.max()
end
use ExUnit.Case
test "123" do
assert [
15_887_950,
16_495_136,
527_345,
704_524,
1_553_684,
12_683_156,
11_100_544,
12_249_484,
7_753_432,
5_908_254
] == Stream.iterate(123, &D22.evolve/1) |> Stream.drop(1) |> Enum.take(10)
end
test "part1" do
input = "1
10
100
2024
"
assert 37_327_623 == D22.parse(input) |> D22.part1()
end
test "part2" do
assert 6 == sell([-1, -1, 0, 2], collate(123))
assert [7, 7, 0, 9] == Enum.map([1, 2, 3, 2024], &sell([-2, 1, -1, 3], collate(&1)))
input = "1
2
3
2024
"
assert 23 == D22.parse(input) |> D22.part2()
end
end
ExUnit.run()
input = File.read!(__DIR__ <> "/input")
D22.parse(input) |> D22.part1()
D22.parse(input) |> D22.part2()