Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 17

day17.livemd

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 &amp;&amp;
     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], &amp;String.to_integer/1)
    program = String.split(program, ",") |> Enum.map(&amp;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