Source code for day04.day4

"""Day 4 solution."""

INPUT = "day04/input.txt"
INPUT_SMALL = "day04/input-small.txt"


[docs] def split_numbers(string: str) -> set[int]: """Splits a list of string'ed numbers into a set. E.g: `` 39 40 41 42 `` -> ``set(39,40,41,42)`` Args: string (str): a list of string'ed numbers Returns: set[int]: a set of integers """ string = string.strip() return {int(number) for number in string.split()}
[docs] class Card: """a card with winners and numbers we own.""" id: int = 0 winners: set[int] have: set[int] def __init__(self, input_string: str): """Construct a Card from a simple input string. Args: input_string (str): ``Card 1: 41 48 | 83 86`` """ line = input_string.strip() card_id_str, numbers_str = line.split(":") winners_str, have_str = numbers_str.split("|") self.id = int(card_id_str.split()[1]) self.winners = split_numbers(winners_str) self.have = split_numbers(have_str)
[docs] def get_points(self) -> int: """Returns how many points the card is worth. Returns: int: 0 for no match, otherwise 2^(matches-1) """ matches = self.get_matches() if matches == 0: points = 0 else: points = 2 ** (matches - 1) return points
[docs] def get_matches(self) -> int: """Returns how many winners intersect with what we have.""" intersection = self.winners.intersection(self.have) return len(intersection)
[docs] class Inventory: """Total inventory of cards based on Question2 accumulation.""" # mapping of card to how many more cards it makes memoized: dict[int, int] all_cards: list[Card] def __init__(self, all_cards: list[Card]): """An inventory from a list of cards. Args: all_cards (list[Card]): list of original cards """ self.all_cards = all_cards self.memoized = self.calculate_mappings()
[docs] def calculate_mappings(self) -> dict[int, int]: """Returns map of card_id -> cards owned.""" mappings = {} reversed_cards = self.all_cards[::-1] for card in reversed_cards: matches = card.get_matches() if matches == 0: mappings[card.id] = 1 else: total = sum(mappings[card.id + i] for i in range(1, matches + 1)) mappings[card.id] = 1 + total return mappings
[docs] def total_cards(self) -> int: """Return total cards in inventory.""" return sum(self.memoized.values())
[docs] def grab_data(filename: str) -> list[Card]: """Converts file into wellformed cards.""" with open(filename, "r", encoding="utf8") as file: result = [Card(line) for line in file] return result
[docs] def part1(cards: list[Card]) -> int: """Return sum of points for each card in list.""" return sum(card.get_points() for card in cards)
[docs] def part2(cards: list[Card]) -> int: """Return total number of cards in our inventory.""" inventory: Inventory = Inventory(cards) return inventory.total_cards()
[docs] def main() -> None: """Loads input file then runs part1 and part2.""" cards: list[Card] = grab_data(INPUT) # Q1 print(part1(cards)) # Q2 print(part2(cards))
if __name__ == "__main__": main()