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

Advent of Code - Day 02

02.livemd

Advent of Code - Day 02

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Introduction

–> Content

Puzzle

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

Parser

Code - Parser

defmodule Game do
  defstruct [:id, :draws]

  def valid?(%Game{draws: draws}, bag) do
    Enum.map(draws, fn draw ->
      Enum.map(draw, fn {color, value} -> bag[color] >= value end)
      |> Enum.all?()
    end)
    |> Enum.all?()
  end
end
defmodule Parser do
  def parse(input) do
    [game_str, games] = String.split(input, ":")
    %Game{id: parse_game_str(game_str), draws: parse_games(games)}
  end

  defp parse_game_str(game_str) do
    Regex.run(~r/Game (\d+)/, game_str, capture: :all_but_first)
    |> hd()
    |> String.to_integer()
  end

  defp parse_games(games) do
    games
    |> String.split(";")
    |> Enum.map(fn draw ->
      draw
      |> String.split(",")
      |> Enum.map(&String.trim(&1))
      |> parse_draw()
    end)
  end

  defp parse_draw(draw) do
    draw
    |> Enum.into(%{}, fn r ->
      [number, color] = Regex.run(~r/(\d+) (\w)/, r, capture: :all_but_first)
      {String.to_atom(color), String.to_integer(number)}
    end)
  end
end

Tests - Parser

ExUnit.start(autorun: false)

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

  @input "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red"
  @expected %Game{id: 4, draws: [%{r: 3, b: 6, g: 1}, %{r: 6, g: 3}, %{r: 14, b: 15, g: 3}]}

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

ExUnit.run()

Part One

Code - Part 1

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

  def run(input, bag) do
    input
    |> String.split("\n")
    |> Enum.map(&Parser.parse(&1))
    |> Enum.filter(fn game -> Game.valid?(game, bag) end)
    |> Enum.map(fn %Game{id: id} -> id end)
    |> IO.inspect()
    |> Enum.sum()
  end
end

Tests - Part 1

ExUnit.start(autorun: false)

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

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

  test "part one" do
    actual = run(@input, %{r: 12, g: 13, b: 14})
    assert actual == @expected
  end
end

ExUnit.run()

Solution - Part 1

PartOne.solve(puzzle_input, %{r: 12, g: 13, b: 14})

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
    |> String.split("\n")
    |> Enum.map(&Parser.parse(&1))
    |> Enum.map(fn %Game{draws: draws} ->
      Enum.reduce(draws, %{r: 0, g: 0, b: 0}, fn d, accu ->
        accu =
          case accu.r < Map.get(d, :r, 0) do
            true -> Map.replace(accu, :r, d.r)
            _ -> accu
          end

        accu =
          case accu.g < Map.get(d, :g, 0) do
            true -> Map.replace(accu, :g, d.g)
            _ -> accu
          end

        accu =
          case accu.b < Map.get(d, :b, 0) do
            true -> Map.replace(accu, :b, d.b)
            _ -> accu
          end

        accu
      end)
    end)
    |> Enum.map(fn %{r: r, g: g, b: b} -> r * g * b end)
    |> Enum.sum()
  end
end

Tests - Part 2

ExUnit.start(autorun: false)

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

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

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

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)