defmodule Day17 do defmodule Computer do @derive Inspect @enforce_keys [:a, :b, :c, :out, :instructions, :pointer] defstruct @enforce_keys def from_data(data) do [a, b, c | instructions] = Regex.scan(~r/\d+/, data) |> Enum.map(&String.to_integer(hd(&1))) %Computer{a: a, b: b, c: c, out: [], instructions: instructions, pointer: 0} end def run(comp) do case instruction(comp) do {:halt, comp} -> comp {:ok, comp} -> run(comp) end end defp instruction(comp) do case comp.instructions |> Enum.drop(comp.pointer) do [] -> {:halt, comp} [0, r | _] -> cr = combo(comp, r) {:ok, %Computer{comp | a: Bitwise.bsr(comp.a, cr), pointer: comp.pointer+2}} [1, r | _] -> {:ok, %Computer{comp | b: Bitwise.bxor(comp.b, r), pointer: comp.pointer+2}} [2, r | _] -> cr = combo(comp, r) {:ok, %Computer{comp | b: rem(cr, 8), pointer: comp.pointer+2}} [3, r | _] -> if comp.a == 0 do {:ok, %Computer{comp | pointer: comp.pointer+2}} else {:ok, %Computer{comp | pointer: r}} end [4, _ | _] -> {:ok, %Computer{comp | b: Bitwise.bxor(comp.b, comp.c), pointer: comp.pointer+2}} [5, r | _] -> cr = combo(comp, r) {:ok, %Computer{comp | out: [rem(cr, 8) | comp.out], pointer: comp.pointer+2}} [6, r | _] -> cr = combo(comp, r) {:ok, %Computer{comp | b: Bitwise.bsr(comp.a, cr), pointer: comp.pointer+2}} [7, r | _] -> cr = combo(comp, r) {:ok, %Computer{comp | c: Bitwise.bsr(comp.a, cr), pointer: comp.pointer+2}} end end defp combo(comp, v) do case v do 0 -> 0 1 -> 1 2 -> 2 3 -> 3 4 -> comp.a 5 -> comp.b 6 -> comp.c 7 -> raise "Invalid combo operand (7)" end end end def part1(data) do data |> Computer.from_data() |> Computer.run() |> then(& &1.out) |> Enum.reverse() |> Enum.join(",") end def part2(data) do data |> Computer.from_data() |> then(& &1.instructions) |> Enum.reverse() |> solve2(0) end defp solve2(rout, a) do case rout do [] -> a [h | t] -> # I deduced the following by hand for my input, but maybe it could # have been done programatically. An important takeway is that # (for my input, and probably for all) `a` incerases by 3 bits for each # iteration of the program. AS such, we can only need to check the # possibilities over the last 3 bits (a..a+7) in each iteration ourselves a = Bitwise.bsl(a, 3) possible = a..a+7 |> Enum.filter(fn a -> Bitwise.band(a, 7) |> Bitwise.bxor(5) |> Bitwise.bxor(6) |> Bitwise.bxor(Bitwise.bsr(a, Bitwise.band(a, 7) |> Bitwise.bxor(5))) |> Bitwise.band(7) == h end) case possible do [] -> :none possible -> possible |> Enum.map(& solve2(t, &1)) |> Enum.find(& &1 != :none) || :none end end end end data = IO.read(:stdio, :eof) {time1, ans1} = :timer.tc(fn -> Day17.part1(data) end) IO.puts("Time : #{time1 / 1000000}") IO.puts("Answer: #{ans1}") {time2, ans2} = :timer.tc(fn -> Day17.part2(data) end) IO.puts("Time : #{time2 / 1000000}") IO.puts("Answer: #{ans2}")