Powered by AppSignal & Oban Pro

Day 4

notebooks/day04.livemd

Day 4

Mix.install([
  {:req, "~> 0.4.5"},
  {:kino, "~> 0.11.3"}
])

Input

session = System.fetch_env!("LB_AOC_SESSION")

input =
  Req.get!(
    "https://adventofcode.com/2023/day/4/input",
    headers: [{"Cookie", ~s"session=#{session}"}]
  ).body

Kino.Text.new(input, terminal: true)

Part 1

lines = String.split(input, "\n", trim: true)

parse_line = fn line ->
  [_, line] = String.split(line, ":")
  [win, have] = String.split(line, "|")
  win_nums = Regex.scan(~r/\d+/, win) |> List.flatten() |> Enum.map(&String.to_integer/1)
  have_nums = Regex.scan(~r/\d+/, have) |> List.flatten() |> Enum.map(&String.to_integer/1)
  {win_nums, have_nums}
end

cards = Enum.map(lines, parse_line)

matches = fn {win, have} ->
  MapSet.new(win)
  |> MapSet.intersection(MapSet.new(have))
  |> MapSet.size()
end

score = fn card ->
  won = matches.(card)
  if won == 0, do: 0, else: 2 ** (won - 1)
end

cards
|> Enum.map(score)
|> Enum.sum()

Part 2

num_cards = length(cards)

initial_copies =
  1..num_cards
  |> Enum.map(fn id -> {id, 1} end)
  |> Map.new()

cards
|> Enum.with_index(1)
|> Enum.reduce(initial_copies, fn {card, id}, copies ->
  won = matches.(card)

  if won == 0 do
    copies
  else
    multiplier = copies[id]
    start_id = id + 1
    stop_id = min(id + won, num_cards)

    for copy_id <- start_id..stop_id,
        reduce: copies do
      copies -> Map.update!(copies, copy_id, fn existing -> existing + multiplier end)
    end
  end
end)
|> Map.values()
|> Enum.sum()