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

Advent of Code 2024 - Day 11

elixir/2024/day_11.livemd

Advent of Code 2024 - Day 11

Mix.install([
  :kino_aoc,
  :memoize
])

Introduction

2024 - Day 11

Puzzle

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

Parser

Code - Parser

defmodule Parser do
  def parse(input) do
    input
    |> String.split("\s", trim: true)
    |> Enum.map(&String.to_integer/1)
  end
end

Tests - Parser

ExUnit.start(autorun: false)

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

  @input "1 2"
  @expected [1, 2]

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

ExUnit.run()

Shared

defmodule Shared do
  use Memoize

  def run(input, max_step) do
    Memoize.invalidate(Shared, :blink)

    input
    |> Parser.parse()
    |> Enum.map(fn num -> blink(num, max_step, 0) end)
    |> Enum.sum()
  end

  defmemop blink(_, max_step, step) when step >= max_step, do: 1
  defmemop blink(0, max_step, step), do: blink(1, max_step, step + 1)

  defmemop blink(num, max_step, step) do
    digits = Integer.digits(num) |> Enum.count()

    cond do
      rem(digits, 2) != 0 ->
        blink(num * 2024, max_step, step + 1)

      true ->
        divisor = Integer.pow(10, div(digits, 2))
        left = div(num, divisor)
        right = rem(num, divisor)
        blink(left, max_step, step + 1) + blink(right, max_step, step + 1)
    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: Shared.run(input, 25)
end

Tests - Part 1

ExUnit.start(autorun: false)

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

  @input "125 17"
  @expected 55312

  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: Shared.run(input, 75)
end

Tests - Part 2

ExUnit.start(autorun: false)

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

  @input "125 17"
  @expected 65601038650482

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

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)