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

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(&amp;String.to_integer/1)
    end)
    |> then(&amp;[String.to_integer(String.trim(card)) | &amp;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(&amp;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(&amp;if &amp;1 == 0, do: [], else: (card_id + 1)..(&amp;1 + card_id))
    |> Enum.to_list()
    # prepend the card id to the list of ranges
    |> then(&amp;[card_id, &amp;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, &amp;Map.get(acc, &amp;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(&amp; &amp;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