|
|
@@ -0,0 +1,202 @@ |
|
|
|
from helpers import debug, load_input |
|
|
|
|
|
|
|
def is_digit(n): |
|
|
|
try: |
|
|
|
m = int(n) |
|
|
|
except ValueError: |
|
|
|
return False |
|
|
|
return True |
|
|
|
|
|
|
|
def is_star(n): |
|
|
|
return n == "*" |
|
|
|
|
|
|
|
input_numbers = [] |
|
|
|
stars = [] |
|
|
|
|
|
|
|
class InputNumber: |
|
|
|
def __init__(self, number, start_pos, end_pos, line): |
|
|
|
self.number = number |
|
|
|
self.start_pos = start_pos |
|
|
|
self.end_pos = end_pos |
|
|
|
self.line = line |
|
|
|
|
|
|
|
def _is_symbol(self, char): |
|
|
|
return (char != "." and not is_digit(char)) |
|
|
|
|
|
|
|
def _check_up(self, input): |
|
|
|
if self.line == 0: |
|
|
|
return False |
|
|
|
cur_line = input[self.line - 1] |
|
|
|
for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)): |
|
|
|
char = cur_line[i] |
|
|
|
if self._is_symbol(char): |
|
|
|
debug(f"Found symbol next to {self.number} at line {self.line-1}: {i}") |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def _check_down(self, input): |
|
|
|
if self.line == len(input)-1: |
|
|
|
return False |
|
|
|
cur_line = input[self.line + 1] |
|
|
|
for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)): |
|
|
|
char = cur_line[i] |
|
|
|
if self._is_symbol(char): |
|
|
|
debug(f"Found symbol next to {self.number} at line {self.line+1}: {i}") |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def _check_left(self, input): |
|
|
|
if self.start_pos == 0: |
|
|
|
debug("Already at the left-hand side of the line") |
|
|
|
return False |
|
|
|
if self._is_symbol(input[self.line][self.start_pos-1]): |
|
|
|
debug(f"Found symbol next to {self.number} at line {self.line}: {self.start_pos-1}") |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def _check_right(self, input): |
|
|
|
if self.end_pos == len(input[0]) - 1: |
|
|
|
debug("Already at the right-hand side of the line") |
|
|
|
return False |
|
|
|
if self._is_symbol(input[self.line][self.end_pos+1]): |
|
|
|
debug(f"Found symbol next to {self.number} at line {self.line}: {self.end_pos+1}") |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def is_part_number(self, input): |
|
|
|
return ( |
|
|
|
self._check_up(input) |
|
|
|
or self._check_down(input) |
|
|
|
or self._check_left(input) |
|
|
|
or self._check_right(input) |
|
|
|
) |
|
|
|
|
|
|
|
def thats_me(self, line, pos): |
|
|
|
return self.line == line and pos in range(self.start_pos, self.end_pos+1) |
|
|
|
|
|
|
|
class Star: |
|
|
|
def __init__(self, line, pos): |
|
|
|
self.line = line |
|
|
|
self.pos = pos |
|
|
|
self.adjacent_numbers = [] |
|
|
|
|
|
|
|
def _check_up(self, input): |
|
|
|
if self.line == 0: |
|
|
|
debug("Already at the top") |
|
|
|
return False |
|
|
|
found_one = False |
|
|
|
for i in range(self.pos-1, self.pos+2): |
|
|
|
debug(f"Checking {self.line-1}:{i}") |
|
|
|
char = input[self.line-1][i] |
|
|
|
if is_digit(char): |
|
|
|
target_number = [number for number in input_numbers if number.thats_me(self.line-1, i)][0] |
|
|
|
if target_number not in self.adjacent_numbers: |
|
|
|
self.adjacent_numbers.append(target_number) |
|
|
|
found_one = True |
|
|
|
return found_one |
|
|
|
|
|
|
|
def _check_down(self, input): |
|
|
|
if self.line == len(input) - 1: |
|
|
|
debug("Already at the bottom") |
|
|
|
return False |
|
|
|
found_one = False |
|
|
|
for i in range(self.pos-1, self.pos+2): |
|
|
|
debug(f"Checking {self.line+1}:{i}") |
|
|
|
char = input[self.line+1][i] |
|
|
|
if is_digit(char): |
|
|
|
target_number = [number for number in input_numbers if number.thats_me(self.line+1, i)][0] |
|
|
|
if target_number not in self.adjacent_numbers: |
|
|
|
self.adjacent_numbers.append(target_number) |
|
|
|
found_one = True |
|
|
|
return found_one |
|
|
|
|
|
|
|
def _check_left(self, input): |
|
|
|
if self.pos == 0: |
|
|
|
debug ("Already at the left side") |
|
|
|
return False |
|
|
|
debug(f"Checking {self.line}:{self.pos-1}") |
|
|
|
char = input[self.line][self.pos-1] |
|
|
|
if is_digit(char): |
|
|
|
target_number = [number for number in input_numbers if number.thats_me(self.line, self.pos-1)][0] |
|
|
|
if target_number not in self.adjacent_numbers: |
|
|
|
self.adjacent_numbers.append(target_number) |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def _check_right(self, input): |
|
|
|
if self.pos == len(input[0]) - 1: |
|
|
|
debug ("Already at the right side") |
|
|
|
return False |
|
|
|
debug(f"Checking {self.line}:{self.pos+1}") |
|
|
|
char = input[self.line][self.pos+1] |
|
|
|
if is_digit(char): |
|
|
|
target_number = [number for number in input_numbers if number.thats_me(self.line, self.pos+1)][0] |
|
|
|
if target_number not in self.adjacent_numbers: |
|
|
|
self.adjacent_numbers.append(target_number) |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def _is_gear(self, input): |
|
|
|
self._check_up(input) |
|
|
|
self._check_down(input) |
|
|
|
self._check_left(input) |
|
|
|
self._check_right(input) |
|
|
|
l_adj = len(self.adjacent_numbers) |
|
|
|
n_adj = [number.number for number in self.adjacent_numbers] |
|
|
|
debug(f"Star at {self.line}:{self.pos} has {l_adj} adjacent number{'s' if l_adj > 1 else ''}: {n_adj}") |
|
|
|
return len(self.adjacent_numbers) == 2 |
|
|
|
|
|
|
|
def gear_value(self, input): |
|
|
|
if self._is_gear(input): |
|
|
|
return self.adjacent_numbers[0].number * self.adjacent_numbers[1].number |
|
|
|
return 0 |
|
|
|
|
|
|
|
def main(): |
|
|
|
lines = load_input(3) |
|
|
|
# Input is lines of periods (.), numbers (0-9), and symbols |
|
|
|
# (anything that isn't a period or number) |
|
|
|
# Any number adjacent (horizontally, vertically, diagonally) |
|
|
|
# to a symbol is a part number. Get the sum of the part numbers. |
|
|
|
max_len = len(lines[0]) # all lines are the same length |
|
|
|
for j, line in enumerate(lines): |
|
|
|
i = 0 |
|
|
|
while i < max_len: |
|
|
|
if is_star(line[i]): |
|
|
|
star = Star( |
|
|
|
line=j, |
|
|
|
pos=i |
|
|
|
) |
|
|
|
stars.append(star) |
|
|
|
# debug(f"Found a star at {star.line}:{star.pos}") |
|
|
|
elif is_digit(line[i]): |
|
|
|
current_number = "" |
|
|
|
start_pos = i |
|
|
|
while i < max_len and is_digit(line[i]): |
|
|
|
current_number += f"{line[i]}" |
|
|
|
i += 1 |
|
|
|
if i < max_len and is_star(line[i]): |
|
|
|
star = Star( |
|
|
|
line=j, |
|
|
|
pos=i |
|
|
|
) |
|
|
|
stars.append(star) |
|
|
|
# debug(f"Found a star next to a number at {star.line}:{star.pos}") |
|
|
|
end_pos = i |
|
|
|
input_number = InputNumber( |
|
|
|
number = int(current_number), |
|
|
|
start_pos = start_pos, |
|
|
|
end_pos = end_pos-1, # i is one greater than the end of the number |
|
|
|
line = j |
|
|
|
) |
|
|
|
input_numbers.append(input_number) |
|
|
|
# debug(f"Found {input_number.number} at line {input_number.line} ({input_number.start_pos}, {input_number.end_pos})") |
|
|
|
i += 1 |
|
|
|
# Now I have a list of the positions of all the numbers and stars in the input. |
|
|
|
gear_values = [] |
|
|
|
for s in stars: |
|
|
|
gear_values.append(s.gear_value(lines)) |
|
|
|
|
|
|
|
print(sum(gear_values)) |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
main() |