Powered by AppSignal & Oban Pro

AOC 2023 - Day 02

aoc2023/day02.livemd

AOC 2023 - Day 02

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

AOC Helper

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

Part 1

Code

defmodule PartOne do
  defp valid_game?(game, counts) do
    game
    |> String.split("; ")
    |> Enum.map(fn subset ->
      subset
      |> String.split(", ")
      |> Enum.map(fn block ->
        [number, color] = String.split(block, " ")

        {color, String.to_integer(number)}
      end)
    end)
    |> Enum.map(&validate_blocks(&1, counts))
    |> Enum.all?()
  end

  defp validate_blocks(blocks, {red_count, green_count, blue_count}) do
    blocks
    |> Enum.map(fn {color, count} ->
      case color do
        "red" -> count <= red_count
        "green" -> count <= green_count
        "blue" -> count <= blue_count
      end
    end)
    |> Enum.all?()
  end

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

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.reduce(0, fn "Game " <> game, total ->
      [id, detail] = String.split(game, ": ")

      if valid_game?(detail, {12, 13, 14}) do
        total + String.to_integer(id)
      else
        total
      end
    end)
  end
end

Test

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)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartOne.solve(puzzle_input)

Part 2

Code

defmodule PartTwo do
  defp game_power(game) do
    game
    |> String.split("; ")
    |> Enum.reduce({0, 0, 0}, fn subset, {red_count, green_count, blue_count} ->
      subset
      |> String.split(", ")
      |> Enum.reduce({red_count, green_count, blue_count}, fn block,
                                                              {red_count, green_count, blue_count} ->
        [count, color] = String.split(block, " ")

        case color do
          "red" -> {max(red_count, String.to_integer(count)), green_count, blue_count}
          "green" -> {red_count, max(green_count, String.to_integer(count)), blue_count}
          "blue" -> {red_count, green_count, max(blue_count, String.to_integer(count))}
        end
      end)
    end)
  end

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

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.reduce(0, fn "Game " <> game, total ->
      [_id, detail] = String.split(game, ": ")

      detail
      |> game_power()
      |> Tuple.to_list()
      |> IO.inspect(label: "powers")
      |> Enum.reduce(1, &(&1 * &2))
      |> Kernel.+(total)
    end)
  end
end

Test

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

PartTwo.solve(puzzle_input)