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

Day 13

day13.livemd

Day 13

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/13/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"}
])

# 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

# === Part 1 Example:
# Button A: X+94, Y+34
# Button B: X+22, Y+67
# Prize: X=8400, Y=5400

# Button A: X+26, Y+66
# Button B: X+67, Y+21
# Prize: X=12748, Y=12176

# Button A: X+17, Y+86
# Button B: X+84, Y+37
# Prize: X=7870, Y=6450

# Button A: X+69, Y+23
# Button B: X+27, Y+71
# Prize: X=18641, Y=10279

# === Part 2 Example:
# Add 10000000000000 to each of the prize values
# thus px and py are close to a 1:1 ratio 
# so we should be able to calculate the ratio
# of the two magnitudes of the A and B vectors
# and approximate the prize values

defmodule Day13 do
  @parser ~r/Button A: X\+(\d+), Y\+(\d+)\nButton B: X\+(\d+), Y\+(\d+)\nPrize: X=(\d+), Y=(\d+)/
  @max_button_presses 100
  @a_cost 3
  @b_cost 1
  @part2offset 10000000000000
  
  def parse(text) do
    text
    |> AOC.as_doublespaced_paragraphs()
    |> Enum.reduce([], fn paragraph, acc ->
      [ax, ay, bx, by, px, py] = Regex.scan(@parser, paragraph, multiline: true)
        |> List.flatten()
        |> Enum.slice(1..-1//1)
        |> Enum.map(&amp;String.to_integer/1)

      acc ++ [%{ax: ax, ay: ay, bx: bx, by: by, px: px, py: py}]
    end)
  end

  def solve1(text) do
    machines = parse(text)

    machines
    |> Enum.reduce(0, fn %{ax: ax, ay: ay, bx: bx, by: by, px: px, py: py} = _machine, acc ->
      # IO.inspect(machine, label: "machine")
      [axp_max, ayp_max, bxp_max, byp_max] = [px / ax, py / ay, px / bx, py / by]
        |> Enum.map(&amp;ceil/1)
      
      list = for a_presses <- 0..Enum.min([@max_button_presses, axp_max, ayp_max]), 
        b_presses <- 0..Enum.min([@max_button_presses, bxp_max, byp_max]),
        ((ax * a_presses + bx * b_presses == px) &amp;&amp; (ay * a_presses + by * b_presses == py)), 
        do: (@a_cost * a_presses + @b_cost * b_presses)

      if list == [] do
        acc
      else
        acc + (Enum.min(list) || 0)
      end
    end)
  end

  def solve2(text) do
    machines = parse(text)
      |> Enum.map(fn machine ->
        machine
        |> Map.update!(:px, &amp;(&amp;1 + @part2offset))
        |> Map.update!(:py, &amp;(&amp;1 + @part2offset))
      end)

    machines
    |> Enum.reduce(0, fn %{ax: ax, ay: ay, bx: bx, by: by, px: px, py: py} = _machine, acc ->
      # IO.inspect(machine, label: "machine")

      # From:
      # https://old.reddit.com/r/adventofcode/comments/1hd7irq/2024_day_13_an_explanation_of_the_mathematics/
      # I was on the right track but re-deriving the equation was unlikely
      # (although the px/py slope approached 1:1 which may have helped)
      a_presses = floor((px * by - py * bx) / (ax * by - ay * bx))
      b_presses = floor((ax * py - ay * px) / (ax * by - ay * bx))
      if a_presses * ax + b_presses * bx == px &amp;&amp;
        a_presses * ay + b_presses * by == py do
        acc + (@a_cost * a_presses + @b_cost * b_presses)
      else
        acc
      end
    end)
  end
end

data.()
|> Day13.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: 480)")
# 29201

data.()
|> Day13.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 875318608908)")
# 104140871044942