AOC 23 - Day 04 - Scratchcards

Link to Advent of Code day 4

Part 1

Rules

We need to help the Elf to find which scratchcards has winning numbers. Here is the pool of cards:

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

Each line represent a card, with the numbers separated in 2 categories: the numbers left to | are the winning numbers, and the numbers to the right are the numbers you have. Finding a winning number give you a point, and each additional number multiply the points you have by 2.

Code

My first though is to parse these cards in sets, and calculate the intersections to get the amount of points.

Scratchcard = Tuple[Set[int], Set[int]]


def part_one(input_file: str) -> int:
    scratchcards = get_scratchcards(input_file)
    total = 0
    for (winning_numbers, numbers) in scratchcards:
        points = len(winning_numbers & numbers)
        if points > 0:
            total += 2 ** (points -1)
    return total


def get_scratchcards(input_file: str) -> List[Scratchcard]:
    scratchcards = []
    with open(input_file) as f:
        for line in f.readlines():
            left, right = line.split(":")[1].split("|")
            scratchcard = set(map(int, left.split())), set(map(int, right.split()))
            scratchcards.append(scratchcard)
    return scratchcards

It works, and was easy to code enough.

Part 2

Rules

For the part 2, the points for a given card give you copies of the cards after the one you are scratching. For example, if you were to win 2 points with the card 10, you would have one copy of card 11 and one copy of card 12. You need to continue until you can't progress further, and then returns the amount of scratchcards you now have The text states:

Cards will never make you copy a card past the end of the table.

Code

The rule "Cards will never make you copy a card past the end of the table" means we can probably just iterate from the first to the last table, and keep an inventory of the points. I use a defaultdict to keep track of the count, the rest is quite straightforward:

def part_two(input_file: str) -> int:
    scratchcards = get_scratchcards(input_file)
    scratchcards_count = defaultdict(lambda: 1)
    for idx, (winning_numbers, numbers) in enumerate(scratchcards):
        points = len(winning_numbers & numbers)
        current_ticket_nbr = scratchcards_count[idx]
        for point in range(1, points + 1):
            scratchcards_count[idx + point] += current_ticket_nbr
    return sum(scratchcards_count.values())

This is enough to solve the second part.


All the code is available on github