Skip to content

Commit f00d038

Browse files
committed
fix: add initial public modules
1 parent 89fc81e commit f00d038

File tree

6 files changed

+143
-172
lines changed

6 files changed

+143
-172
lines changed

.pre-commit-config.yaml

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
repos:
2-
- repo: https://github.com/astral-sh/ruff-pre-commit
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
33
rev: v0.3.0
44
hooks:
5-
- id: ruff
5+
- id: ruff
66
args: [--fix]
7-
- id: ruff-format
7+
- id: ruff-format
88

9-
- repo: https://github.com/pre-commit/mirrors-mypy
9+
- repo: https://github.com/pre-commit/mirrors-mypy
1010
rev: v1.8.0
1111
hooks:
12-
- id: mypy
13-
additional_dependencies: [
14-
"types-requests>=2.32.0",
15-
"types-beautifulsoup4>=4.12.0"
16-
]
12+
- id: mypy
13+
additional_dependencies:
14+
["types-requests>=2.32.0", "types-beautifulsoup4>=4.12.0"]
1715

18-
- repo: https://github.com/pre-commit/pre-commit-hooks
16+
- repo: https://github.com/pre-commit/pre-commit-hooks
1917
rev: v4.5.0
2018
hooks:
21-
- id: trailing-whitespace
22-
- id: end-of-file-fixer
23-
- id: check-yaml
24-
- id: check-toml
25-
- id: check-added-large-files
19+
- id: trailing-whitespace
20+
- id: end-of-file-fixer
21+
- id: check-yaml
22+
- id: check-toml
23+
- id: check-added-large-files

2023/solutions/day01.py

Lines changed: 35 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1+
"""Day 1: Trebuchet?!
2+
3+
This module provides the solution for Advent of Code 2023 - Day 1.
4+
It handles extraction of calibration values from strings containing digits
5+
and written numbers.
6+
7+
The module contains a Solution class that inherits from SolutionBase and implements
8+
methods to extract and process calibration values from input strings.
9+
"""
10+
111
import re
212

313
from aoc.models.base import SolutionBase
414

515

616
class Solution(SolutionBase):
7-
"""Solution for Advent of Code 2023 - Day 1: Trebuchet?!
17+
"""Extract and process calibration values from strings.
818
9-
This class solves a puzzle involving calibration values embedded in strings,
10-
where the values need to be extracted by finding the first and last digits
11-
in each string. Part 2 extends this to include digits written as words.
12-
13-
Input format:
14-
List of strings where each line contains a mixture of:
15-
- Single digits (0-9)
16-
- Letters and other characters
17-
- Written number words (one, two, three, etc.)
19+
This solution handles strings containing calibration values that need to be
20+
extracted by finding the first and last digits. Part 1 handles numeric digits
21+
only, while Part 2 extends this to include digits written as words.
1822
1923
The solution uses regex pattern matching to find both numeric digits and
2024
written numbers, converting them to their numerical representations.
@@ -34,60 +38,39 @@ class Solution(SolutionBase):
3438
pattern = r"(?=(one|two|three|four|five|six|seven|eight|nine|\d))"
3539

3640
def part1(self, data: list[str]) -> int:
37-
"""Extract and sum calibration values using only numeric digits.
38-
39-
Processes each line to find the first and last numeric digits,
40-
combining them to form a two-digit number. If only one digit
41-
is found, it's used as both first and last digit.
41+
"""Calculate the sum of calibration values using only numeric digits.
4242
4343
Args:
44-
data: List of strings containing calibration values
44+
data (list[str]): List of strings containing calibration values.
4545
4646
Returns
4747
-------
48-
Sum of all calibration values
48+
The sum of all calibration values, where each value is formed by
49+
combining the first and last numeric digits in each string.
4950
"""
50-
values = []
51-
for row in data:
52-
digits = [char for char in row if char.isdigit()]
51+
total = 0
52+
for line in data:
53+
digits = [c for c in line if c.isdigit()]
5354
if digits:
54-
digit = int(digits[0] + digits[-1]) if len(digits) > 1 else int(digits[0] * 2)
55-
values.append(digit)
56-
57-
return sum(values)
55+
total += int(digits[0] + digits[-1])
56+
return total
5857

5958
def part2(self, data: list[str]) -> int:
60-
"""Extract and sum calibration values using both digits and written numbers.
61-
62-
Processes each line to find the first and last occurrence of either
63-
numeric digits or written number words (e.g., "one", "two", etc.).
64-
Written numbers are converted to their digit equivalents before
65-
combining them to form a two-digit number.
66-
67-
Uses positive lookahead regex pattern to handle overlapping matches
68-
(e.g., "oneight" contains both "one" and "eight").
59+
"""Calculate the sum of calibration values including written numbers.
6960
7061
Args:
71-
data: List of strings containing calibration values with both
72-
numeric and written numbers
62+
data (list[str]): List of strings containing calibration values and written numbers.
7363
7464
Returns
7565
-------
76-
Sum of all calibration values
66+
The sum of all calibration values, where each value is formed by
67+
combining the first and last digits (numeric or written) in each string.
7768
"""
78-
values = []
79-
for row in data:
80-
digits = []
81-
for match in re.finditer(self.pattern, row):
82-
# group(1) contains the actual matched digit/word
83-
digit = match.group(1)
84-
85-
# Convert word to digit if it's a word
86-
digit = self.number_map.get(digit, digit)
87-
digits.append(digit)
88-
89-
if digits:
90-
value = int(digits[0] + digits[-1])
91-
values.append(value)
92-
93-
return sum(values)
69+
total = 0
70+
for line in data:
71+
matches = re.findall(self.pattern, line)
72+
if matches:
73+
first = self.number_map.get(matches[0], matches[0])
74+
last = self.number_map.get(matches[-1], matches[-1])
75+
total += int(first + last)
76+
return total

2023/solutions/day02.py

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1+
"""Day 2: Cube Conundrum.
2+
3+
This module provides the solution for Advent of Code 2023 - Day 2.
4+
It handles analysis of cube games where colored cubes are drawn from a bag
5+
in multiple sets, with each game having several draws of red, green, and blue cubes.
6+
7+
The module contains a Solution class that inherits from SolutionBase and implements
8+
methods to validate games against thresholds and find minimum required cubes.
9+
"""
10+
111
from math import prod
212
import re
313

414
from aoc.models.base import SolutionBase
515

616

717
class Solution(SolutionBase):
8-
"""Solution for Advent of Code 2023 - Day 2: Cube Conundrum.
18+
"""Analyse cube games for validity and minimum requirements.
919
10-
This class solves a puzzle involving a game where colored cubes are drawn from
11-
a bag in multiple sets. Each game consists of several draws, and each draw
12-
reveals a certain number of red, green, and blue cubes.
13-
14-
Input format:
15-
List of strings where each line represents a game in the format:
16-
"Game N: X red, Y green, Z blue; A red, B green, C blue; ..."
20+
This solution processes games where colored cubes are drawn from a bag.
21+
Part 1 validates games against thresholds for each color.
22+
Part 2 calculates the minimum number of cubes required for each game.
1723
1824
The solution uses regex patterns to extract game IDs and cube counts,
1925
validating them against thresholds (Part 1) and finding minimum required
@@ -27,71 +33,56 @@ class Solution(SolutionBase):
2733
def is_possible(self, data: str) -> bool:
2834
"""Check if a set of cube draws is possible given the thresholds.
2935
30-
Examines each color count in the draw and verifies it doesn't
31-
exceed the predefined thresholds (12 red, 13 green, 14 blue).
32-
3336
Args:
34-
data: String containing cube counts for a single set
35-
Example: "3 blue, 4 red"
37+
data (str): String containing cube counts for a single set
3638
3739
Returns
3840
-------
3941
Boolean indicating whether the draw is possible with given thresholds
4042
"""
41-
# Check if all extracted counts are within the thresholds
4243
for count, color in re.findall(self.color_regex, data):
4344
if int(count) > self.thresholds[color]:
44-
return False # Violates threshold
45+
return False
4546

4647
return True
4748

4849
def part1(self, data: list[str]) -> int:
4950
"""Sum IDs of games possible with the given cube thresholds.
5051
51-
Processes each game to determine if all its sets are possible
52-
given the cube limits (12 red, 13 green, 14 blue). For valid
53-
games, adds their ID to the running sum.
54-
5552
Args:
56-
data: List of strings containing game configurations
53+
data (list[str]): List of game strings in format "Game N: X red, Y green, Z blue; ..."
5754
5855
Returns
5956
-------
60-
Sum of IDs of all possible games
57+
Sum of IDs for games where all draws are within thresholds
6158
"""
62-
count = 0
59+
total = 0
6360
for game in data:
64-
game_id, sets = game.split(":")[0], game.split(":")[-1].strip().split(";")
65-
game_id = int(re.search(self.id_regex, game_id).group(1))
66-
if all([self.is_possible(cubes) for cubes in sets]):
67-
count += game_id
61+
game_id = int(re.search(self.id_regex, game).group(1))
62+
sets = game.split(":")[1].split(";")
63+
if all(self.is_possible(s) for s in sets):
64+
total += game_id
6865

69-
return count
66+
return total
7067

7168
def part2(self, data: list[str]) -> int:
72-
"""Calculate sum of power of minimum cube sets needed for each game.
73-
74-
For each game, determines the minimum number of cubes of each color
75-
that would be needed to make the game possible. The power of a set
76-
is the product of the minimum required numbers of red, green, and
77-
blue cubes.
69+
"""Calculate the sum of minimum cube powers for each game.
7870
7971
Args:
80-
data: List of strings containing game configurations
72+
data (list[str]): List of game strings in format "Game N: X red, Y green, Z blue; ..."
8173
8274
Returns
8375
-------
84-
Sum of the power of minimum required cube sets across all games
76+
Sum of the product of minimum required cubes for each color in each game
8577
"""
8678
total = 0
87-
for row in data:
88-
cubes = {"red": 0, "green": 0, "blue": 0}
89-
game = row.split(":")[-1].strip()
90-
91-
for count, color in re.findall(self.color_regex, game):
92-
if int(count) > cubes[color]:
93-
cubes[color] = int(count)
79+
for game in data:
80+
sets = game.split(":")[1].split(";")
81+
min_cubes = {"red": 0, "green": 0, "blue": 0}
82+
for s in sets:
83+
for count, color in re.findall(self.color_regex, s):
84+
min_cubes[color] = max(min_cubes[color], int(count))
9485

95-
total += prod(cubes.values())
86+
total += prod(min_cubes.values())
9687

9788
return total

2023/solutions/day03.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
1+
"""Day 3: Gear Ratios.
2+
3+
This module provides the solution for Advent of Code 2023 - Day 3.
4+
It handles analysis of engine schematics containing numbers and symbols,
5+
where valid part numbers are determined by their adjacency to symbols.
6+
7+
The module contains a Solution class that inherits from SolutionBase and implements
8+
methods to identify valid part numbers and calculate gear ratios.
9+
"""
10+
111
import math
212
import re
313

414
from aoc.models.base import SolutionBase
515

616

717
class Solution(SolutionBase):
8-
"""Solution for Advent of Code 2023 - Day 3: Gear Ratios.
9-
10-
This class solves a puzzle involving a schematic containing numbers and symbols,
11-
where valid part numbers are determined by their adjacency to symbols. In part 2,
12-
specific attention is paid to 'gears' which are * symbols adjacent to exactly
13-
two numbers.
14-
15-
Input format:
16-
List of strings representing an engine schematic where each line contains:
17-
- Numbers (part numbers)
18-
- Symbols (including *)
19-
- Periods (.) representing empty spaces
18+
"""Analyse engine schematics for valid part numbers and gear ratios.
19+
20+
This solution processes engine schematics containing numbers and symbols.
21+
Part 1 identifies valid part numbers by checking their adjacency to symbols.
22+
Part 2 calculates gear ratios by finding * symbols adjacent to exactly two numbers.
23+
24+
The solution uses regex patterns to identify numbers and symbols, then
25+
analyses their positions to determine valid part numbers and gear ratios.
2026
"""
2127

2228
digit_regex = r"\D"
@@ -32,7 +38,7 @@ def parse_data(
3238
where index uniquely identifies each full number in the schematic.
3339
3440
Args:
35-
data: List of strings representing the engine schematic
41+
data (list[str]): List of strings representing the engine schematic
3642
3743
Returns
3844
-------
@@ -65,13 +71,13 @@ def parse_data(
6571
return nums, symbols
6672

6773
def part1(self, data: list[str]) -> int:
68-
"""Calculate sum of all valid part numbers.
74+
"""Calculate the sum of valid part numbers in the schematic.
6975
70-
A valid part number is any number adjacent (including diagonally)
71-
to a symbol. Numbers adjacent to periods (.) are not valid.
76+
A part number is valid if it is adjacent to any symbol (including diagonally).
77+
The solution parses the schematic and checks each number's adjacency to symbols.
7278
7379
Args:
74-
data: List of strings representing the engine schematic
80+
data (list[str]): List of strings representing the engine schematic
7581
7682
Returns
7783
-------
@@ -88,13 +94,13 @@ def part1(self, data: list[str]) -> int:
8894
return sum(item[0] for item in set(adjacent_nums))
8995

9096
def part2(self, data: list[str]) -> int:
91-
"""Calculate sum of gear ratios.
97+
"""Calculate the sum of gear ratios in the schematic.
9298
93-
A gear is a * symbol that is adjacent to exactly two numbers.
94-
The gear ratio is the product of those two numbers.
99+
A gear is a * symbol that is adjacent to exactly two part numbers.
100+
The gear ratio is the product of these two numbers.
95101
96102
Args:
97-
data: List of strings representing the engine schematic
103+
data (list[str]): List of strings representing the engine schematic
98104
99105
Returns
100106
-------

0 commit comments

Comments
 (0)