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

Day13

2024/elixir/day13.livemd

Day13

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])

Setup

{:ok, data} = KinoAOC.download_puzzle("2024", "13", System.fetch_env!("LB_AOC_SECRET"))

Solve

defmodule Day13 do
  @button_re ~r/X\+(\d+), Y\+(\d+)/
  @prize_re  ~r/X=(\d+), Y=(\d+)/

  def parse(input, offset) do
    input
    |> String.trim()
    |> String.split("\n", trim: true)
    |> Enum.chunk_every(3)
    |> Enum.map(&parse_machine(&1, offset))
  end

  def parse_machine([a, b, prize], offset) do
    %{
      a: parse_line(a, @button_re),
      b: parse_line(b, @button_re),
      prize: parse_line(prize, @prize_re, offset)
    }
  end

  def parse_line(line, re, offset \\ 0) do
    [x, y] = Regex.run(re, line, capture: :all_but_first)
    {String.to_integer(x) + offset, String.to_integer(y) + offset}
  end

  def solve(data, offset \\ 10_000_000_000_000) do
    data
    |> parse(offset)
    |> Enum.map(&solve_machine/1)
    |> Enum.reduce({0, 0}, fn
      {:ok, {a, b}}, {total, count} ->
        {total + calc_tokens(a, b), count + 1}

      :no_solution, acc ->
        acc
    end)
    |> then(fn {total, count} ->
      IO.puts("solvable: #{count}")
      IO.puts("tokens needed: #{total}")
      String.duplicate("-", 30) |> IO.puts()
      total
    end)
  end

  @doc """
  Cramer's rule - https://www.purplemath.com/modules/cramers.htm

  Or can be solved without ☝️ knowledge by looking into equations:
  A * ax + B * bx = px
  A * ay + B * by = py

  and finding A & B:
  A = (by * px - bx * py) / (ax * by - ay * bx)
  B = (py - ay * a) / by

  there is no solution:
  - if (ax * by - ay * bx) is zero
  or
  - if A & B are rationals or negative or both zero
  (number of times to push a button can't be negative or rational)
  """
  def solve_machine(%{a: {ax, ay}, b: {bx, by}, prize: {px, py}}) do
    # calculate determinant of the coefficient matrix
    det = ax * by - ay * bx

    # det == 0 means that the system of equations has no unique solution
    if det == 0 do
      :no_solution
    else
      # calculate determinants for each equation
      da = px * by - py * bx
      db = ax * py - ay * px

      # dividing variable's determinant by the coefficient determinant
      # using integer division
      a = div(da, det)
      b = div(db, det)

      # verify solution is valid
      if a >= 0 and b >= 0 and
         ax * a + bx * b == px and
         ay * a + by * b == py do
        {:ok, {a, b}}
      else
        :no_solution
      end
    end
  end

  def calc_tokens(a_presses, b_presses), do: 3 * a_presses + b_presses
end


tdata = """
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
"""

Day13.solve(tdata, 0) # 480
Day13.solve(tdata)    # 875318608908
Day13.solve(data, 0)  # 29187
Day13.solve(data)     # 99968222587852
:ok