Day 17
# Note: when making the next template, something like this works well:
# `cat day04.livemd | sed 's/17/04/' > day04.livemd`
# When inspecting lists of numbers, use "charlists: :as_lists"
#
Mix.install([
# Join the string so a copy of dayN to dayM doesn't destroy it.
{:kino, "~> 0.1" <> "4.2"}
])
import Integer
import Bitwise
# Join the string so a copy of dayN to dayM doesn't destroy it.
IEx.Helpers.c("/Users/johnb/dev/2" <> "0" <> "2" <> "4adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input
Installation and Data
input_example = Kino.Input.textarea("Example Data", monospace: true)
input_puzzleInput = Kino.Input.textarea("Puzzle Input", monospace: true)
input_source_select =
Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
data = fn ->
(Kino.Input.read(input_source_select) == :example &&
Kino.Input.read(input_example)) ||
Kino.Input.read(input_puzzleInput)
end
Solution
defmodule Day17 do
@parser ~r/Register A: (\d+)\nRegister B: (\d+)\nRegister C: (\d+)\n\nProgram: (.*)/
def parse_input(text) do
[[_, a, b, c, program]] = Regex.scan(@parser, text, multiline: true)
[a, b, c] = Enum.map([a, b, c], &String.to_integer/1)
program = String.split(program, ",") |> Enum.map(&String.to_integer/1)
[a, b, c, program]
end
def translate_operand(operand, aa, bb, cc) do
case operand do
0 -> operand
1 -> operand
2 -> operand
3 -> operand
4 -> aa
5 -> bb
6 -> cc
7 -> :halt
end
end
def run_program(a, b, c, program) do
program_length = Enum.count(program)
1..100_000
|> Enum.reduce_while([a, b, c, [], 0], fn _iteration, [aa, bb, cc, oout, ip] ->
if ip >= program_length do
{:halt, oout}
else
[opcode, operand] = Enum.slice(program, ip..(ip+1))
combo = translate_operand(operand, aa, bb, cc)
case opcode do
0 -> {:cont, [floor(aa / (Integer.pow(2, combo))), bb, cc, oout, ip + 2]}
1 -> {:cont, [aa, bxor(bb, operand), cc, oout, ip + 2]}
2 -> {:cont, [aa, rem(combo, 8), cc, oout, ip + 2]}
3 -> if aa == 0 do
{:cont, [aa, bb, cc, oout, ip + 2]}
else
{:cont, [aa, bb, cc, oout, operand]}
end
4 -> {:cont, [aa, bxor(bb, cc), cc, oout, ip + 2]}
5 -> {:cont, [aa, bb, cc, oout ++ [rem(combo, 8)], ip + 2]}
6 -> {:cont, [aa, floor(aa / (Integer.pow(2, combo))), cc, oout, ip + 2]}
7 -> {:cont, [aa, bb, floor(aa / (Integer.pow(2, combo))), oout, ip + 2]}
end
end
end)
end
def solve1(text) do
[a, b, c, program] = parse_input(text)
run_program(a, b, c, program)
end
# Example:
# Register A: 2024
# Register B: 2024
# Register C: 43690
# Program: 4,0
def solve2(_text) do
b = 0
c = 0
program = [2,4,1,7,7,5,0,3,4,4,1,7,5,5,3,0]
pstring = Enum.join(program)
Enum.reduce(program, [0], fn _p, aaas ->
next_set = Enum.reduce(aaas, [], fn a, next_aaas ->
Enum.reduce(0..7, next_aaas, fn bits, acc ->
maybe_a = a * 8 + bits
test = run_program(maybe_a, b, c, program) |> Enum.join()
if String.ends_with?(pstring, test) do
AOC.inspect(test, label: "A=#{maybe_a}")
acc ++ [maybe_a]
else
acc
end
end)
end)
next_set -- aaas
end)
end
end
data.()
|> Day17.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: '4,6,3,5,6,3,5,2,1,0')")
# "2,1,0,1,7,2,5,0,3" (for A=52042868)
data.()
|> Day17.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (quine)")
IO.puts("Part 2 is aiming for output of '2,4,1,7,7,5,0,3,4,4,1,7,5,5,3,0'")
# 267265166222235 is the smaller of the 2 A values that re-produce the program