|
| 1 | +from typing import Dict, List, Tuple |
| 2 | + |
| 3 | +from aoc.models.base import SolutionBase |
| 4 | + |
| 5 | + |
| 6 | +class TowelSorter: |
| 7 | + """Solution helper for arranging towel patterns. |
| 8 | +
|
| 9 | + This class processes towel arrangements where each towel has a specific pattern of |
| 10 | + colored stripes. It determines whether sequences can be created using available |
| 11 | + towel patterns and counts the number of unique ways to arrange towels to match |
| 12 | + desired sequences. |
| 13 | +
|
| 14 | + Attributes: |
| 15 | + towels (List[str]): Available towel patterns |
| 16 | + sequences (List[str]): Desired stripe sequences to match |
| 17 | + cache (Dict[str, int]): Memoization cache for sequence arrangements |
| 18 | + """ |
| 19 | + |
| 20 | + def __init__(self, towels: List[str], sequences: List[str]) -> None: |
| 21 | + """Initialize TowelSorter with available patterns and target sequences. |
| 22 | +
|
| 23 | + Args: |
| 24 | + towels (List[str]): List of available towel patterns |
| 25 | + sequences (List[str]): List of sequences to match |
| 26 | + """ |
| 27 | + self.towels = towels |
| 28 | + self.sequences = sequences |
| 29 | + self.cache: Dict[str, int] = {} |
| 30 | + |
| 31 | + def count_ways(self, seq: str) -> int: |
| 32 | + """Count number of unique ways to arrange towels to match a sequence. |
| 33 | +
|
| 34 | + Uses dynamic programming with memoization to efficiently count arrangements |
| 35 | + without building complete arrangement lists. |
| 36 | +
|
| 37 | + Args: |
| 38 | + seq (str): Target sequence to match |
| 39 | +
|
| 40 | + Returns: |
| 41 | + int: Number of unique ways to arrange towels to match sequence |
| 42 | + """ |
| 43 | + if not seq: |
| 44 | + return 1 |
| 45 | + |
| 46 | + if seq in self.cache: |
| 47 | + return self.cache[seq] |
| 48 | + |
| 49 | + total = 0 |
| 50 | + for towel in self.towels: |
| 51 | + if seq.startswith(towel): |
| 52 | + total += self.count_ways(seq[len(towel) :]) |
| 53 | + |
| 54 | + self.cache[seq] = total |
| 55 | + return total |
| 56 | + |
| 57 | + def is_possible(self, seq: str) -> bool: |
| 58 | + """Check if a sequence can be created using available towel patterns. |
| 59 | +
|
| 60 | + Args: |
| 61 | + seq (str): Sequence to check |
| 62 | +
|
| 63 | + Returns: |
| 64 | + bool: True if sequence can be created, False otherwise |
| 65 | + """ |
| 66 | + return self.count_ways(seq) > 0 |
| 67 | + |
| 68 | + def part1(self) -> int: |
| 69 | + """Count how many sequences are possible with available towel patterns. |
| 70 | +
|
| 71 | + Returns: |
| 72 | + int: Number of sequences that can be created |
| 73 | + """ |
| 74 | + return sum(1 for seq in self.sequences if self.is_possible(seq)) |
| 75 | + |
| 76 | + def part2(self) -> int: |
| 77 | + """Sum up the number of unique ways each sequence can be created. |
| 78 | +
|
| 79 | + Returns: |
| 80 | + int: Total number of possible arrangements across all sequences |
| 81 | + """ |
| 82 | + return sum(self.count_ways(seq) for seq in self.sequences) |
| 83 | + |
| 84 | + |
| 85 | +class Solution(SolutionBase): |
| 86 | + """Solution for Advent of Code 2023 - Day 19: Linen Layout. |
| 87 | +
|
| 88 | + This class solves a puzzle about arranging towels with colored stripe patterns. |
| 89 | + Part 1 determines which sequences are possible to create, while Part 2 counts |
| 90 | + the total number of unique ways to arrange towels for each sequence. |
| 91 | +
|
| 92 | + Input format: |
| 93 | + - First line: comma-separated list of available towel patterns |
| 94 | + - Blank line |
| 95 | + - Remaining lines: sequences to match using available patterns |
| 96 | +
|
| 97 | + This class inherits from `SolutionBase` and provides methods to parse input data, |
| 98 | + check sequence possibility, and count unique arrangements. |
| 99 | + """ |
| 100 | + |
| 101 | + def parse_data(self, data: List[str]) -> Tuple[List[str], List[str]]: |
| 102 | + """Parse input data into towel patterns and target sequences. |
| 103 | +
|
| 104 | + Args: |
| 105 | + data (List[str]): Raw input lines |
| 106 | +
|
| 107 | + Returns: |
| 108 | + Tuple[List[str], List[str]]: Tuple of (towel patterns, sequences to match) |
| 109 | + """ |
| 110 | + towels, seq = "\n".join(data).split("\n\n") |
| 111 | + return towels.split(", "), [row for row in seq.split("\n") if row] |
| 112 | + |
| 113 | + def part1(self, data: List[str]) -> int: |
| 114 | + """Solve part 1: Count possible sequences. |
| 115 | +
|
| 116 | + Args: |
| 117 | + data (List[str]): Input data lines |
| 118 | +
|
| 119 | + Returns: |
| 120 | + int: Number of sequences that can be created with available patterns |
| 121 | + """ |
| 122 | + towels, seq = self.parse_data(data) |
| 123 | + towel_sorter = TowelSorter(towels, seq) |
| 124 | + return towel_sorter.part1() |
| 125 | + |
| 126 | + def part2(self, data: List[str]) -> int: |
| 127 | + """Solve part 2: Count total possible arrangements. |
| 128 | +
|
| 129 | + Args: |
| 130 | + data (List[str]): Input data lines |
| 131 | +
|
| 132 | + Returns: |
| 133 | + int: Sum of possible arrangements for all sequences |
| 134 | + """ |
| 135 | + towels, seq = self.parse_data(data) |
| 136 | + towel_sorter = TowelSorter(towels, seq) |
| 137 | + return towel_sorter.part2() |
0 commit comments