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

AoC 2024, Day 2

d02.livemd

AoC 2024, Day 2

Section

defmodule Round do
  defstruct red: 0, green: 0, blue: 0

  def parse(round) when is_binary(round) do
    String.split(round, ", ")
    |> Enum.map(fn entry ->
      [n, color] = String.split(entry)
      {String.to_existing_atom(color), String.to_integer(n)}
    end)
    |> then(fn fields -> struct!(Round, fields) end)
  end
end
{:module, Round, <<70, 79, 82, 49, 0, 0, 11, ...>>, {:parse, 1}}
defmodule Game do
  defstruct [:id, :rounds]

  def parse(game) when is_binary(game) do
    [id, rounds] = String.split(game, ": ")
    ["Game", id] = String.split(id)
    rounds = String.split(rounds, "; ") |> Enum.map(&amp;Round.parse/1)
    %Game{id: String.to_integer(id), rounds: rounds}
  end

  @doc """
  Checks if `game` is possible given a max number of cubes of each color
  specified in `limit` as a `Round` struct.
  """
  def possible?(%__MODULE__{} = game, %Round{} = limit) do
    Enum.all?(game.rounds, fn round ->
      round.red <= limit.red &amp;&amp; round.green <= limit.green &amp;&amp; round.blue <= limit.blue
    end)
  end

  @doc """
  Returns a `Round` struct containing the minimal required number of cubes
  of each color for `game` to be possible.
  """
  def required(%__MODULE__{} = game) do
    Enum.reduce(game.rounds, %Round{}, fn round, required ->
      %Round{
        red: max(round.red, required.red),
        green: max(round.green, required.green),
        blue: max(round.blue, required.blue)
      }
    end)
  end
end
{:module, Game, <<70, 79, 82, 49, 0, 0, 19, ...>>, {:required, 1}}
defmodule D02 do
  def read_input() do
    Path.join(__DIR__, "inputs/d02")
    |> File.read!()
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;Game.parse/1)
  end

  @p1_limit %Round{red: 12, green: 13, blue: 14}

  def p1(games) do
    games
    |> Enum.filter(&amp;Game.possible?(&amp;1, @p1_limit))
    |> Enum.map(fn g -> g.id end)
    |> Enum.sum()
  end

  def p2(games) do
    Enum.map(games, fn game ->
      %Round{red: red, green: green, blue: blue} = Game.required(game)
      red * green * blue
    end)
    |> Enum.sum()
  end
end
{:module, D02, <<70, 79, 82, 49, 0, 0, 12, ...>>, {:p2, 1}}
ExUnit.start(autorun: false)

defmodule D01Test do
  use ExUnit.Case, async: true

  test "parser works" do
    game = "Game 1: 2 red, 2 green; 6 red, 3 green; 2 red, 1 green, 2 blue; 1 red"

    assert Game.parse(game) == %Game{
             id: 1,
             rounds: [
               %Round{red: 2, green: 2},
               %Round{red: 6, green: 3},
               %Round{red: 2, green: 1, blue: 2},
               %Round{red: 1}
             ]
           }
  end

  @test_games_raw """
  Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
  Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
  Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
  Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
  Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
  """
  @test_games String.split(@test_games_raw, "\n", trim: true) |> Enum.map(&amp;Game.parse/1)

  test "part 1 works" do
    assert D02.p1(@test_games) == 8
    assert D02.read_input() |> D02.p1() == 2169
  end

  test "part 2 works" do
    assert D02.p2(@test_games) == 2286
    assert D02.read_input() |> D02.p2() == 60948
  end
end

ExUnit.run()
...
Finished in 0.02 seconds (0.02s async, 0.00s sync)
3 tests, 0 failures

Randomized with seed 793685
%{total: 3, failures: 0, excluded: 0, skipped: 0}