diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/elixir/livebook/2024/day17.livemd b/elixir/livebook/2024/day17.livemd index f5cd1fd..d8b4ca7 100644 --- a/elixir/livebook/2024/day17.livemd +++ b/elixir/livebook/2024/day17.livemd @@ -46,6 +46,32 @@ If register B contains 29, the program 1,7 would set register B to 26. If register B contains 2024 and register C contains 43690, the program 4,0 would set register B to 44354. ``` +```elixir +e1 = """ +Register A: 729 +Register B: 0 +Register C: 0 + +Program: 0,1,5,4,3,0 +""" +s1 = "4,6,3,5,6,3,5,2,1,0" + +e2 = """ +Register A: 2024 +Register B: 0 +Register C: 0 + +Program: 0,3,5,4,3,0 +""" +input = """ +Register A: 17323786 +Register B: 0 +Register C: 0 + +Program: 2,4,1,1,7,5,1,5,4,1,5,5,0,3,3,0 +""" +``` + ```elixir defmodule ChronospatialComputer do defstruct a: nil, b: nil, c: nil, program: nil, pc: nil, output: <<>> @@ -55,7 +81,12 @@ defmodule ChronospatialComputer do [a,b,c] = for register <- registers |> String.split("\n") do register |> String.split(": ") |> Enum.at(1) |> String.to_integer end - pc = program |> String.split(": ") |> Enum.at(1) |> String.split(",", trim: true) |> Enum.map(&(String.to_integer/1)) |> :binary.list_to_bin() + pc = program + |> String.split(": ") + |> Enum.at(1) + |> String.split(",", trim: true) + |> Enum.map(&(String.to_integer/1)) + |> :binary.list_to_bin() init(a,b,c,pc) end @@ -151,106 +182,90 @@ defmodule ChronospatialComputer do 7 -> throw "received combo op 7 at #{computer.pc + 1}" end end + + # Runs my AOC input written as Elixir with `a` in register A. + # I could use the code that solves p1, but this runs 10x faster. + def run_with_register_a(a), do: run_with_register_a(a, <<>>) + def run_with_register_a(0, o), do: o + def run_with_register_a(a, o) do + import Bitwise + # 0 to 7 + # 0 <= len(b) <= 3 + b = (a &&& 7)|> Bitwise.bxor(1) + # a - 7 <= len(c) <= a + c = a >>> b + # 0 <= len(b) <= 3 + b = b |> Bitwise.bxor(5) |> Bitwise.bxor(c) + o = o <> <> + run_with_register_a(a >>> 3, o) + end + + # finds valid values of the `A` register that will cause `run_with_register_a` to return + # an output equal to the program + def quine(program) do + quine(program, 0, 10, 0) + |> Enum.sort() + end + + def quine(<<>>, bits, _, _), do: [bits] + # bits is the integer so far + # len is the number of bits to generate + # z is the number of bits in `bits` expressed as 7 + 3*z + def quine(<< first, rest::binary >>, bits, len, z) do + import Bitwise + + for i <- 0..((2**len)-1), # first iter - generate 10-bit sequences, do 3-bits on all subsequent loops + # noop on first iter - after, shifts over 7 bits + i = i <<< (10 - len), + # `bits` is always 7+3z bits long, so this will return 7 bits from `bits` and + # adds the three bits from `i` at the beginning + # so j is << i_0, i_1, i_2, b_0, b_1, b_2, b_3, b_4, b_5, b_6 >> + j = (bits >>> 3*z) + i, + # run the program with `j` as input + o = ChronospatialComputer.run_with_register_a(j), + o |> byte_size() > 0, + # take only the sequences that generate the next output we're looking for + # if we're on the last iteration we want sequences that don't generate + # more outputs than we need. + # if we're on any other iteration, we take those outputs because they + # will be combined with more bits on the next loop. + (rest != <<>> and o |> :binary.at(0) == first) or o == << first >> do + # takes the `len` (either 3 or 10 - if it's the first loop) new bits from + # `i` and prepends them to `bits` + (i <<< 3*z) + bits + end |> Enum.flat_map( + fn b -> + # generate the next three bits in the sequence + # we do 3 at a time because the program always shifts a 3 bits after writing + # to the output. + quine(rest, b, 3, z+1) + end + ) + end + end ``` -```elixir -<<12>> <> <<12>> +Digging deeper in the device's manual, you discover the problem: this program is supposed to output another copy of the program! Unfortunately, the value in register A seems to have been corrupted. You'll need to find a new value to which you can initialize register A so that the program's output instructions produce an exact copy of the program itself. + +For example: + ``` - -```elixir -e1 = """ -Register A: 729 -Register B: 0 -Register C: 0 - -Program: 0,1,5,4,3,0 -""" -s1 = "4,6,3,5,6,3,5,2,1,0" - -e2 = """ Register A: 2024 Register B: 0 Register C: 0 Program: 0,3,5,4,3,0 -""" -input = """ -""" +``` + +This program outputs a copy of itself if register A is instead initialized to 117440. (The original initial value of register A, 2024, is ignored.) + +What is the lowest positive initial value for register A that causes the program to output a copy of itself? + +```elixir +[p2answer, _] = ChronospatialComputer.quine(<<2,4,1,1,7,5,1,5,4,1,5,5,0,3,3,0>>) ``` ```elixir -comp = input |> ChronospatialComputer.parse() -``` - -```elixir -Stream.iterate(0, &(&1 + 1)) -|> Enum.reduce_while( - nil, - fn i, _ -> - case (%ChronospatialComputer{comp | a: i} - |> ChronospatialComputer.run_while(fn c -> - o = c.output - match?(^o <> _, c.program) - end)) do - {:halt, %ChronospatialComputer{program: p, output: p}} -> {:halt, i} - _ -> {:cont, nil} - end - end -) -``` - -```elixir -""" -Register A: 0 -Register B: 0 -Register C: 9 - -Program: 2,6 -""" -|> ChronospatialComputer.parse() |> ChronospatialComputer.run() -``` - -```elixir -""" -Register A: 10 -Register B: 0 -Register C: 0 - -Program: 5,0,5,1,5,4 -""" -|> ChronospatialComputer.parse() |> ChronospatialComputer.run() -``` - -```elixir -""" -Register A: 2024 -Register B: 0 -Register C: 0 - -Program: 0,1,5,4,3,0 -""" -|> ChronospatialComputer.parse() |> ChronospatialComputer.run() -``` - -```elixir -""" -Register A: 0 -Register B: 29 -Register C: 0 - -Program: 1,7 -""" -|> ChronospatialComputer.parse() |> ChronospatialComputer.run() -``` - -```elixir -""" -Register A: 0 -Register B: 2024 -Register C: 43690 - -Program: 4,0 -""" -|> ChronospatialComputer.parse() |> ChronospatialComputer.run() +ChronospatialComputer.run_with_register_a(p2answer) ```