Powered by AppSignal & Oban Pro

Day 4: Scratchcards – Advent of Code

2023/04.livemd

Day 4: Scratchcards – Advent of Code

input =
  File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/04.txt")
  |> Stream.map(&String.trim/1)

test_input =
  "Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11"
  |> String.split("\n")

Parse lines

parse_cards = fn lines ->
  lines
  |> Stream.map(&String.split(&1, ~r"[:\|]"))
  |> Stream.map(fn ["Card " <> card, winnings, chosen] ->
    [winnings, chosen]
    |> Enum.map(fn lst ->
      lst |> String.split(" ", trim: true) |> Enum.map(&String.to_integer/1)
    end)
    |> then(&[String.to_integer(String.trim(card)) | &1])
  end)
end

Part 1

part_one = fn lines ->
  lines
  |> parse_cards.()
  # |> Stream.map(fn [_card, a, b] -> a -- (a -- b) end) # alt for function below
  |> Stream.map(fn [_card, a, b] ->
    MapSet.intersection(MapSet.new(a), MapSet.new(b)) |> MapSet.to_list()
  end)
  |> Stream.map(&Enum.count/1)
  |> Stream.map(fn c -> if c == 0, do: 0, else: 2 ** (c - 1) end)
  |> Enum.sum()
end

# smoke test
part_one.(test_input) == 13

part_one.(input)

Part 2

# append the winning ranges (next cards)
card_and_wins = fn cards ->
  cards
  |> Stream.map(fn [card_id, wins, chosen] ->
    MapSet.intersection(MapSet.new(wins), MapSet.new(chosen))
    |> MapSet.to_list()
    # winnings count
    |> Enum.count()
    |> then(&if &1 == 0, do: [], else: (card_id + 1)..(&1 + card_id))
    |> Enum.to_list()
    # prepend the card id to the list of ranges
    |> then(&[card_id, &1])
  end)
end

part_two = fn lines ->
  lines
  |> parse_cards.()
  |> card_and_wins.()
  # start bottom up
  |> Enum.reverse()
  # create map with *all* wins
  |> Enum.reduce(%{}, fn [card, wins], acc ->
    Map.put(acc, card, [card | Enum.map(wins, &Map.get(acc, &1))])
  end)
  # convert map to list
  |> Enum.reduce([], fn {_card, v}, acc -> [v | acc] end)
  # |> Enum.flat_map(fn {_card, v} -> if is_map(v), do: v, else: [v] end) # same as above
  |> List.flatten()
  |> Enum.frequencies_by(& &1)
  |> Enum.reduce(0, fn {_card, count}, total -> total + count end)
end

# smoke test
part_two.(test_input) == 30

part_two.(input)

Run in Livebook