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

Advent of Code 2024 - Day 13

elixir/2024/day_13.livemd

Advent of Code 2024 - Day 13

Mix.install([
  :kino_aoc
])

Introduction

2024 - Day 13

Puzzle

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

Parser

Code - Parser

defmodule Parser do
  @reg ~r/\d+/
  
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.chunk_every(3, 3, :discard)
    |> Enum.map(fn [button_a, button_b, prize] ->
      [[x_a], [y_a]] = Regex.scan(@reg, button_a)
      [[x_b], [y_b]] = Regex.scan(@reg, button_b)
      [[x_p], [y_p]] = Regex.scan(@reg, prize)
      [x_a, x_b, x_p, y_a, y_b, y_p]
      |> Enum.map(&String.to_integer/1)
    end)
  end
end

Tests - Parser

ExUnit.start(autorun: false)

defmodule ParserTest do
  use ExUnit.Case, async: true
  import Parser

  @input """
  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
  """
  @expected [[94, 22, 8400, 34, 67, 5400], [26, 67, 12748, 66, 21, 12176]]

  test "parse test" do
    actual = parse(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Shared

defmodule Shared do
  def solve_equation([a1, b1, c1, a2, b2, c2]) do
    d = a1 * b2 - a2 * b1

    if d == 0 do
      nil
    else
      dx = c1 * b2 - c2 * b1
      dy = a1 * c2 - a2 * c1

      rem_x = rem(dx, d)
      rem_y = rem(dy, d)

      x = div(dx, d)
      y = div(dy, d)
      if x < 0 or y < 0 or rem_x != 0 or rem_y != 0 do
        nil
      else
        {x, y}
      end
    end
  end
end

Part One

Code - Part 1

defmodule PartOne do
  def solve(input) do
    IO.puts("--- Part One ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> Parser.parse()
    |> Enum.map(fn params ->
      case Shared.solve_equation(params) do
        nil -> 0
        {a, b} -> a * 3 + b
      end
    end)
    |> Enum.sum()
  end
end

Tests - Part 1

ExUnit.start(autorun: false)

defmodule PartOneTest do
  use ExUnit.Case, async: true
  import PartOne

  @input """
  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
  """
  @expected 480

  test "part one" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution - Part 1

PartOne.solve(puzzle_input)

Part Two

Code - Part 2

defmodule PartTwo do
  def solve(input) do
    IO.puts("--- Part Two ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> Parser.parse()
    |> Enum.map(fn [x_a, x_b, x_p, y_a, y_b, y_p] ->
      x_p = 10_000_000_000_000 + x_p
      y_p = 10_000_000_000_000 + y_p

      case Shared.solve_equation([x_a, x_b, x_p, y_a, y_b, y_p]) do
        nil -> 0
        {a, b} -> a * 3 + b
      end
    end)
    |> Enum.sum()
  end
end

Tests - Part 2

ExUnit.start(autorun: false)

defmodule PartTwoTest do
  use ExUnit.Case, async: true
  import PartTwo

  @input """
  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
  """
  @expected 875_318_608_908

  test "part two" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)