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

Day 02

2023/day-02.livemd

Day 02

Mix.install([
  {:kino, "~> 0.11"},
  {:nimble_parsec, "~> 1.4"}
])

example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")

Common

parse = fn input ->
  for {line, line_idx} <- input |> Kino.Input.read() |> String.split("\n") |> Enum.with_index(1),
      [_, line] = String.split(line, ": "),
      {set, set_idx} <- String.split(line, "; ") |> Enum.with_index(1),
      count_and_color <- String.split(set, ", "),
      [count, color] = String.split(count_and_color, " "),
      count = String.to_integer(count),
      reduce: {%{}, %{}} do
    {acc, maxes} ->
      {Map.update(acc, {line_idx, set_idx, color}, count, &amp;(&amp;1 + count)),
       Map.put(maxes, line_idx, set_idx)}
  end
end

Part 1

{games, maxes} =
  real_input
  |> then(parse)

game_max = Map.keys(maxes) |> Enum.max()

for {game, set_count} <- maxes,
    set <- 1..set_count,
    {color, cutoff} <- [{"red", 12}, {"green", 13}, {"blue", 14}],
    reduce: Map.new(1..game_max, &amp;{&amp;1, true}) do
  acc ->
    case Map.get(games, {game, set, color}, 0) do
      count when count > cutoff -> Map.delete(acc, game)
      _ -> acc
    end
end
|> Map.keys()
|> Enum.sum()

Part 2

{games, _maxes} =
  real_input
  |> then(parse)

games
|> Enum.reduce(%{}, fn {{game, _, color}, count}, acc ->
  Map.update(acc, {game, color}, count, &amp;max(&amp;1, count))
end)
|> Enum.reduce(%{}, fn {{game, _}, min}, acc -> Map.update(acc, game, min, &amp;(&amp;1 * min)) end)
|> Map.values()
|> Enum.sum()

NimbleParsec

defmodule Parser do
  import NimbleParsec

  game_id = ignore(string("Game ")) |> integer(min: 1, max: 4) |> unwrap_and_tag(:id)

  red = replace(string("red"), :red)
  green = replace(string("green"), :green)
  blue = replace(string("blue"), :blue)

  color = choice([red, green, blue])

  cube_separator = optional(string(", "))

  cube =
    integer(min: 1, max: 4)
    |> ignore(string(" "))
    |> concat(color)
    |> ignore(optional(cube_separator))

  round_separator = optional(string("; "))

  round =
    times(cube, min: 1)
    |> ignore(round_separator)
    |> reduce(:reduce_round)

  rounds = times(round, min: 1) |> tag(:rounds)

  game_separator = ascii_string([?\n], 1)

  game =
    game_id
    |> ignore(string(": "))
    |> concat(rounds)
    |> ignore(game_separator)
    |> wrap()
    |> map({Map, :new, []})

  games = times(game, min: 1)

  defparsec(:parse, games |> eos())

  defp reduce_round(cubes) do
    cubes
    |> Enum.chunk_every(2)
    |> Enum.reduce(%{}, fn [count, color], acc ->
      Map.update(acc, color, count, &amp;(&amp;1 + count))
    end)
  end
end

Parser.parse("""
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
""")