from collections.abc import Iterable from pathlib import Path class Card: num: int winning: set[int] picks: set[int] def __init__(self, num: int, winning: set[int], picks: set[int]) -> None: self.num = num self.winning = winning self.picks = picks self.won = 0 def matching(self) -> int: return len(self.winning & self.picks) def score(self) -> int: return int(2**(self.matching()-1)) def cards_won(self) -> Iterable[int]: # print(f"Card {self.num} wins cards {[self.num + x + 1 for x in range(self.matching())]}") for x in range(self.matching()): # print(f"Card {self.num} wins card {self.num + x + 1}") yield self.num + x + 1 def parse_line(line: str) -> Card: card_str, _, nums = line.partition(":") card_num = int(card_str.partition(" ")[2]) winning_str, _, picks_str = nums.partition("|") winning = {int(n.strip()) for n in winning_str.split(" ") if n} picks = {int(n.strip()) for n in picks_str.split(" ") if n.strip()} return Card(card_num, winning, picks) def part1(input: Path) -> int: total = 0 with input.open() as f: for line in f: card = parse_line(line) score = card.score() # print(f"Card {card.num} is worth {score} points") total += score return total def play(cards: list[Card], card_num: int): card = cards[card_num - 1] card.won += 1 for won in card.cards_won(): play(cards, won) def part2(input: Path) -> int: cards: list[Card] = [] with input.open() as f: cards = [parse_line(line) for line in f] for card in range(len(cards)): play(cards, card + 1) total = 0 for card in cards: # print(f"Card {card.num} was won {card.won} times") total += card.won return total if __name__ == "__main__": result1 = part1(Path("input.txt")) print("part 1", result1) result2 = part2(Path("input.txt")) print("part 2", result2)