Skip to content

Commit b596c5f

Browse files
authored
Merge pull request #5 from jameslawlor/ex3-part-2
Ex3 part 2
2 parents 0adb74e + b730641 commit b596c5f

File tree

5 files changed

+312
-145
lines changed

5 files changed

+312
-145
lines changed

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
all: black flake tests solutions
1+
all: black flake test solutions
22

33
black:
4-
black src tests
4+
black ./src ./tests
55

66
flake:
7-
flake8 src tests
7+
flake8 ./src ./tests
88

9-
tests:
9+
test:
1010
python3 -m pytest
1111

1212
solutions:

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ docker run aoc-2023-app
1313
| :---: | :------: | :------: |
1414
| 01 | ⭐️ | ⭐️ |
1515
| 02 | ⭐️ | ⭐️ |
16-
| 03 | ⭐️ | todo |
16+
| 03 | ⭐️ | ⭐️ |
17+
| 04 | todo | todo |

src/aoc_2023/days/1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def main():
2323
calibration_value_sum = solve_all_calibration_values(input, patterns_to_find)
2424
print(
2525
f"Day 1: Solution with accept_written_digits={accept_written_digits}"
26-
f"is {calibration_value_sum}."
26+
f" is {calibration_value_sum}."
2727
)
2828

2929

Lines changed: 164 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,121 @@
11
import re
22

33

4+
class Part:
5+
def __init__(self, part_number):
6+
self.part_number = str(part_number)
7+
self.coordinates = []
8+
self.window = None
9+
self.x_start_coordinate = None
10+
self.x_end_coordinate = None
11+
self.y_coordinate = None
12+
self.full_coordinates = None
13+
14+
def __str__(self):
15+
return (
16+
"\n"
17+
f"part_number = {self.part_number} \n"
18+
f"coordinates = {self.coordinates} \n"
19+
f"window = {self.window} \n"
20+
f"x_start_coordinate = {self.x_start_coordinate} \n"
21+
f"x_end_coordinate = {self.x_end_coordinate} \n"
22+
f"y_coordinate = {self.y_coordinate} \n"
23+
f"full_coordinates = {self.full_coordinates} \n"
24+
)
25+
26+
def set_coordinates(self, y_pos, x_start_pos, x_end_pos):
27+
if x_start_pos > x_end_pos:
28+
raise ValueError(
29+
"Invalid coordinates: "
30+
"x_start_pos should be less than or equal to x_end_pos"
31+
)
32+
self.x_start_coordinate = x_start_pos
33+
self.x_end_coordinate = x_end_pos
34+
self.y_coordinate = y_pos
35+
self.full_coordinates = [(y_pos, x) for x in range(x_start_pos, x_end_pos)]
36+
37+
assert len(self.full_coordinates) == len(self.part_number)
38+
39+
def part_number_as_integer(self):
40+
try:
41+
return int(self.part_number)
42+
except (ValueError, TypeError):
43+
# Handle the case where part_number is not a valid integer
44+
return None
45+
46+
def get_window_around_part(self):
47+
if self.full_coordinates is None:
48+
raise ValueError(
49+
"Part has no full_coordinates! Check Part initialised correctly."
50+
)
51+
52+
self.window = []
53+
for y in [self.y_coordinate - 1, self.y_coordinate, self.y_coordinate + 1]:
54+
for x in range(self.x_start_coordinate - 1, self.x_end_coordinate + 1):
55+
self.window.append((y, x))
56+
57+
def check_window_for_symbol(self, symbol_coordinates):
58+
return bool(set(self.window).intersection(symbol_coordinates))
59+
60+
61+
class Symbol:
62+
def __init__(self, char, y, x):
63+
self.symbol = char
64+
self.y = y
65+
self.x = x
66+
self.coordinates = (y, x)
67+
self.is_potential_gear = self.symbol == "*"
68+
self.is_gear = False
69+
self.window = None
70+
self.neighbouring_parts = None
71+
self.ratio = None
72+
73+
def __str__(self):
74+
return (
75+
"\n"
76+
f"symbol = {self.symbol} \n"
77+
f"y = {self.y} \n"
78+
f"y = {self.x} \n"
79+
f"x = {self.x} \n"
80+
f"coordinates = {self.coordinates} \n"
81+
f"is_potential_gear = {self.is_potential_gear} \n"
82+
f"is_gear = {self.is_gear} \n"
83+
f"window = {self.window} \n"
84+
f"neighbouring_parts = {self.neighbouring_parts} \n"
85+
f"ratio = {self.ratio} \n"
86+
)
87+
88+
def get_surrounding_window(self):
89+
self.window = []
90+
for y in [self.y - 1, self.y, self.y + 1]:
91+
for x in [self.x - 1, self.x, self.x + 1]:
92+
self.window.append((y, x))
93+
94+
def check_window_for_parts(self, parts_collection):
95+
neighbouring_parts = []
96+
for part in parts_collection:
97+
coords_to_check = part.full_coordinates
98+
if bool(set(self.window).intersection(coords_to_check)):
99+
neighbouring_parts.append(part.part_number)
100+
101+
self.neighbouring_parts = neighbouring_parts
102+
103+
def check_if_gear(self):
104+
if not self.is_potential_gear:
105+
raise ValueError("Not a potential gear!")
106+
107+
if len(self.neighbouring_parts) == 2:
108+
part_numbers_as_int = [int(p) for p in self.neighbouring_parts]
109+
self.is_gear = True
110+
self.set_ratio(*part_numbers_as_int)
111+
112+
def set_ratio(self, n1, n2):
113+
if not self.is_gear:
114+
raise ValueError("Ratio can only be set for gears!")
115+
116+
self.ratio = n1 * n2
117+
118+
4119
def check_char_is_number(char) -> bool:
5120
return bool(re.search(r"\d", char))
6121

@@ -9,52 +124,73 @@ def check_char_is_symbol(char) -> bool:
9124
return bool(re.search(r"[^.\d]", char))
10125

11126

12-
def get_symbol_positions(input) -> set:
13-
symbol_positions = []
127+
def get_symbols(input) -> set:
128+
symbols_list = []
14129
for line_number, line in enumerate(input):
15130
for x_pos, char in enumerate(line):
16131
if check_char_is_symbol(char):
17-
symbol_positions.append((line_number, x_pos))
18-
return set(symbol_positions)
19-
20-
21-
def get_window(match_start, match_end, line_number) -> set:
22-
window = []
23-
for y_pos in [line_number - 1, line_number, line_number + 1]:
24-
for x_pos in range(match_start - 1, match_end + 1):
25-
window.append((y_pos, x_pos))
26-
return set(window)
132+
sym = Symbol(char=char, y=line_number, x=x_pos)
133+
symbols_list.append(sym)
134+
return symbols_list
27135

28136

29137
def check_window_for_symbol(window, symbol_positions) -> bool:
30138
return bool(window.intersection(symbol_positions))
31139

32140

33-
def identify_part_numbers(input, symbol_positions) -> list:
34-
part_numbers = []
141+
def identify_part_numbers(input, symbols_collection) -> list:
142+
parts_list = []
143+
144+
symbol_positions = [s.coordinates for s in symbols_collection]
35145

36146
for line_number, line in enumerate(input):
37147
matched_numbers = re.finditer("(\\d+)", line)
38-
line_sum_of_part_numbers = []
39-
40148
for match in matched_numbers:
41149
match_start = match.start()
42150
match_end = match.end()
43151
part_number = match.group()
44-
window = get_window(match_start, match_end, line_number)
45-
if check_window_for_symbol(window, symbol_positions):
46-
part_numbers.append(part_number)
47-
line_sum_of_part_numbers.append(part_number)
152+
part = Part(
153+
part_number=part_number,
154+
)
155+
part.set_coordinates(line_number, match_start, match_end)
156+
part.get_window_around_part()
157+
if part.check_window_for_symbol(symbol_positions):
158+
parts_list.append(part)
159+
160+
for p in parts_list:
161+
print(p)
162+
return parts_list
163+
164+
165+
def identify_gears(parts_collection, symbol_collection):
166+
potential_gears_to_check = [
167+
sym for sym in symbol_collection if sym.is_potential_gear
168+
]
169+
for sym in potential_gears_to_check:
170+
sym.get_surrounding_window()
171+
sym.check_window_for_parts(parts_collection)
172+
sym.check_if_gear()
173+
174+
gear_collection = [sym for sym in symbol_collection if sym.is_gear]
175+
176+
for sym in [sym for sym in symbol_collection if sym.is_potential_gear]:
177+
print(sym)
178+
179+
return gear_collection
180+
48181

49-
return part_numbers
182+
def calculate_sum_of_part_numbers(input_list) -> int:
183+
return sum([part.part_number_as_integer() for part in input_list])
50184

51185

52-
def calculate_sum_of_part_numbers(input_list):
53-
return sum(list(map(int, input_list)))
186+
def calculate_sum_of_gear_ratios(gear_collection) -> int:
187+
return sum([sym.ratio for sym in gear_collection])
54188

55189

56-
def solve_day_3(input):
57-
symbol_positions = get_symbol_positions(input)
58-
list_of_part_numbers = identify_part_numbers(input, symbol_positions)
59-
sum_of_part_numbers = calculate_sum_of_part_numbers(list_of_part_numbers)
60-
return sum_of_part_numbers
190+
def solve_day_3(input) -> int:
191+
symbol_collection = get_symbols(input)
192+
parts_collection = identify_part_numbers(input, symbol_collection)
193+
sum_of_part_numbers = calculate_sum_of_part_numbers(parts_collection)
194+
gear_collection = identify_gears(parts_collection, symbol_collection)
195+
sum_of_gear_ratios = calculate_sum_of_gear_ratios(gear_collection)
196+
return (sum_of_part_numbers, sum_of_gear_ratios)

0 commit comments

Comments
 (0)