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

AoC 2023 - Day 02

2023/lang-elixir/02.livemd

AoC 2023 - Day 02

Mix.install([
  {:kino, "~> 0.11.0"}
])

Section

input_p1t =
  "day02-p1t.txt"
  |> Kino.FS.file_path()
  |> File.read!()
  |> String.trim()
input =
  "day02.txt"
  |> Kino.FS.file_path()
  |> File.read!()
  |> String.trim()
defmodule Game do
  defstruct id: nil, rounds: []
end

defmodule Game.Round do
  defstruct red: 0, green: 0, blue: 0
end
defmodule PartA do
  def solve(input) do
    input
    |> String.split("\n")
    |> Enum.map(fn game ->
      [[_game, id, data]] = Regex.scan(~r/Game (\d+): (.*)/, game)

      %Game{
        id: String.to_integer(id),
        rounds:
          data
          |> String.split(";")
          |> Enum.map(&parse_round/1)
      }
    end)
    |> Enum.filter(&valid?/1)
    |> Enum.map(&Map.get(&1, :id))
    |> Enum.sum()
  end

  def solve_2(input) do
    input
    |> String.split("\n")
    |> Enum.map(fn line ->
      [part_1, part_2] = String.split(line, ":")
      {"Game ", game_id} = String.split_at(part_1, 5)

      rounds =
        part_2
        |> String.trim()
        |> String.split(";")
        |> Enum.map(&parse_round_match/1)

      %Game{
        id: String.to_integer(game_id),
        rounds: rounds
      }
    end)
    |> Enum.filter(&valid?/1)
    |> Enum.map(&Map.get(&1, :id))
    |> Enum.sum()
  end

  def parse_round(data) do
    ~r/(\d+) (red|green|blue)/
    |> Regex.scan(data)
    |> Enum.reduce(%Game.Round{}, &parse_round_item/2)
  end

  def parse_round_match(data) do
    data
    |> String.split(",")
    |> Enum.reduce(%Game.Round{}, fn item, acc ->
      [count, color] = item |> String.trim() |> String.split(" ")

      parse_round_item([nil, count, color], acc)
    end)
  end

  def parse_round_item([_element, count, "red"], %Game.Round{red: r, green: g, blue: b}) do
    %Game.Round{red: r + String.to_integer(count), green: g, blue: b}
  end

  def parse_round_item([_element, count, "green"], %Game.Round{red: r, green: g, blue: b}) do
    %Game.Round{red: r, green: g + String.to_integer(count), blue: b}
  end

  def parse_round_item([_element, count, "blue"], %Game.Round{red: r, green: g, blue: b}) do
    %Game.Round{red: r, green: g, blue: b + String.to_integer(count)}
  end

  def valid?(%Game{rounds: rounds}) do
    rounds |> Enum.map(&valid?/1) |> Enum.all?()
  end

  def valid?(%Game.Round{red: red, green: green, blue: blue})
      when red <= 12 and green <= 13 and blue <= 14,
      do: true

  def valid?(_), do: false
end
[
  {8, PartA.solve(input_p1t)},
  {2265, PartA.solve(input), PartA.solve_2(input)}
]
defmodule PartB do
  def solve(input) do
    input
    |> String.split("\n")
    |> Enum.map(fn line ->
      [_part_1, part_2] = String.split(line, ":")

      part_2
      |> String.trim()
      |> String.split(";")
      |> Enum.flat_map(&amp;String.split(&amp;1, ","))
      |> Enum.map(&amp;(&amp;1 |> String.trim() |> String.split(" ")))
      |> Enum.reduce({0, 0, 0}, &amp;parse_item/2)
    end)
    |> Enum.map(fn {red, green, blue} ->
      red * green * blue
    end)
    |> Enum.sum()
  end

  def solve_2(input) do
    input
    |> String.split("\n")
    |> Enum.map(fn line ->
      [_part_1, part_2] = String.split(line, ":")

      part_2
      |> String.trim()
      |> String.replace(~r/(;\s|,\s)/, " ")
      |> String.split(" ")
      |> Enum.chunk_every(2)
      |> Enum.reduce({0, 0, 0}, &amp;parse_item/2)
    end)
    |> Enum.map(fn {red, green, blue} ->
      red * green * blue
    end)
    |> Enum.sum()
  end

  def parse_item([count, "red"], {r, g, b}) do
    {max(r, String.to_integer(count)), g, b}
  end

  def parse_item([count, "green"], {r, g, b}) do
    {r, max(g, String.to_integer(count)), b}
  end

  def parse_item([count, "blue"], {r, g, b}) do
    {r, g, max(b, String.to_integer(count))}
  end
end
[
  {2286, PartB.solve(input_p1t)},
  {64097, PartB.solve(input), PartB.solve_2(input)}
]