This problem asks us to process scratch cards that list the numbers revealed on the card and the winning numbers. In part 1, we need to count how many revealed numbers are in the winning numbers set with the total points accrued on each card being 1 for the first match and doubling for each subsequent match. In part 2, the number of matches on the card wins the holder copies of subsequent cards with this pattern holding recursively across the copies of cards. The premise can be hard to explain, I struggled to grok it when first reading the full problem statement.

The cards in the puzzle input follow this format.

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

Part 1

This is a problem that cleanly separates into two sub problems: formatting and processing.

The formatting part allows for much easier data manipulation. For this, I wrote a deserialize_card function that takes in a line as formatted in the problem and returns a named tuple. Named tuples in Python behave like tiny, immutable classes. They’re great containers for holding data you want to have travel together through your code.

The output from deserialize_card is used to count how many numbers overlap between the revealed and the winning sets.

from collections import namedtuple

Card = namedtuple("Card", "id mine winning")

def part_1(cards: list[str]) -> int:
    return sum(int(2 ** (count_winning(deserialize_card(card)) - 1)) for card in cards)


def count_winning(card: Card) -> int:
    return len(card.mine.intersection(card.winning))


def deserialize_card(raw: str) -> Card:
    id = raw.split(":")[0]
    mine, winning = raw.split(":")[1].strip().split("|")
    return Card(id,to_set(mine), to_set(winning))

Counting the points has a little trick to it. As described in the problem, the first match is worth 1 point, each subsequent match doubles this value. This is a pattern exhibited by the exponents of 2. So the points earned are 2^n where n is the number of matches.

Part 2

The rules of how points are calculated for this part are best explained by quoting the puzzle itself:

There’s no such thing as “points”. Instead, scratchcards only cause you to win more scratchcards equal to the number of winning numbers you have.

Specifically, you win copies of the scratchcards below the winning card equal to the number of matches. So, if card 10 were to have 5 matching numbers, you would win one copy each of cards 11, 12, 13, 14, and 15.

Copies of scratchcards are scored like normal scratchcards and have the same card number as the card they copied. So, if you win a copy of card 10 and it has 5 matching numbers, it would then win a copy of the same cards that the original card 10 won: cards 11, 12, 13, 14, and 15. This process repeats until none of the copies cause you to win any more cards.

Advent of Code 2023 Day 4