1
0
advent-of-code-2k23/day3/day3.py

117 lines
3.9 KiB
Python
Raw Permalink Normal View History

2023-12-04 14:31:06 +00:00
from typing import Tuple, List, Set
2023-12-03 22:14:42 +00:00
from dataclasses import dataclass
2023-12-03 10:32:47 +00:00
2023-12-03 22:14:42 +00:00
@dataclass(frozen=True)
class Item:
pos: Tuple[int, int]
symbol: str
2023-12-03 10:32:47 +00:00
2023-12-04 14:31:06 +00:00
2023-12-03 10:32:47 +00:00
def browse_schema(schema):
total_parts = 0
buf = []
max_row, max_col = len(schema), len(schema[0])
2023-12-04 14:31:06 +00:00
symbols: List[Tuple[Item, Set[Item]]] = []
2023-12-03 22:14:42 +00:00
numbers: List[Item] = []
2023-12-03 10:32:47 +00:00
for y in range(max_row):
for x in range(max_col):
item = schema[y][x]
if item.isnumeric():
# continue parsing full number
buf.append(item)
2023-12-04 14:31:06 +00:00
else:
neighbors = get_neighbors_of((x, y), schema)
symbols.append((Item((x, y), item), set(neighbors)))
if buf and not item.isnumeric():
2023-12-03 10:32:47 +00:00
# end of a number, do the engine part check
2023-12-03 22:14:42 +00:00
number = "".join(buf)
neighbors = get_neighbors((x, y), len(buf), schema)
start_pos = (x-len(number), y)
2023-12-04 14:31:06 +00:00
symbols.append((Item((x, y), number), get_neighbors_of((x, y), schema)))
2023-12-03 22:14:42 +00:00
numbers.append(Item(start_pos, number))
if is_engine_part(neighbors):
total_parts += int(number)
buf.clear() # reached end of a number, clear buffer
2023-12-03 10:32:47 +00:00
print(f"Part 1, sum of the parts numbers = {total_parts}")
2023-12-04 14:31:06 +00:00
part2(symbols, numbers)
def part2(symbols, numbers):
total_gears = 0
stars = [(s, neighbors) for s, neighbors in symbols if s.symbol == "*"]
for _, neighbors in stars:
corresponding_numbers = set()
digits = [n for n in neighbors if n.symbol.isdigit()]
for digit in digits:
# find full number (number.start_pos < digit.pos < number.end_pos)
for number in numbers:
if number.pos[1] - 1 <= digit.pos[1] <= number.pos[1] + 1 and number.pos[0] <= digit.pos[0] <= number.pos[0]+len(number.symbol):
corresponding_numbers.add(number.symbol)
if len(corresponding_numbers) == 2:
a, b = corresponding_numbers
total_gears += int(a) * int(b)
#print(f"star: {star.pos} {corresponding_numbers}")
print(f"Part 2, sum of gear ratios = {total_gears}")
2023-12-03 10:32:47 +00:00
2023-12-03 22:14:42 +00:00
def is_engine_part(neighbors: List[Item]) -> bool:
2023-12-03 10:32:47 +00:00
# get list of symbols (not '.', \n or a number)
2023-12-03 22:14:42 +00:00
symbols = filter(lambda x: not x.symbol.isnumeric() and not x.symbol in (".", "\n"), neighbors)
2023-12-03 10:32:47 +00:00
return next(symbols, None) is not None
2023-12-03 22:14:42 +00:00
def get_neighbors(pos: Tuple[int, int], length: int, schema: List[List[str]]) -> List[Item]:
2023-12-03 10:32:47 +00:00
x, y = pos
start_x = x - length
2023-12-03 22:14:42 +00:00
neighbors = [get_neighbors_of((x, y), schema) for x in range(start_x, x)]
2023-12-03 10:32:47 +00:00
neighbors = [item for sublist in neighbors for item in sublist] # flatten list of list
return neighbors
2023-12-03 22:14:42 +00:00
def get_neighbors_of(pos: Tuple[int, int], schema: List[List[str]]) -> List[Item]:
2023-12-03 10:32:47 +00:00
max_row, max_col = len(schema), len(schema[0])
x, y = pos
2023-12-03 22:14:42 +00:00
neighbors: List[Item] = []
2023-12-03 10:32:47 +00:00
# top
if y-1 >= 0:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x, y-1), schema[y-1][x]))
2023-12-03 10:32:47 +00:00
# bottom:
if y+1 < max_row:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x, y+1), schema[y+1][x]))
2023-12-03 10:32:47 +00:00
# left
if x-1 >= 0:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x-1, y), schema[y][x-1]))
2023-12-03 10:32:47 +00:00
# right
if x+1 < max_col:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x+1, y), schema[y][x+1]))
2023-12-03 10:32:47 +00:00
# top-left
if y-1 >= 0 and x-1 >= 0:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x-1, y-1), schema[y-1][x-1]))
2023-12-03 10:32:47 +00:00
# top-right
if y-1 >= 0 and x+1 < max_col:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x+1, y-1), schema[y-1][x+1]))
2023-12-03 10:32:47 +00:00
# bottom-left
if y+1 < max_row and x-1 >= 0:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x-1, y+1), schema[y+1][x-1]))
2023-12-03 10:32:47 +00:00
# bottom-right
if y+1 < max_row and x+1 < max_col:
2023-12-03 22:14:42 +00:00
neighbors.append(Item((x+1, y+1), schema[y+1][x+1]))
2023-12-03 10:32:47 +00:00
return neighbors
if __name__ == "__main__":
import sys
infile = sys.argv[1]
with open(infile) as f:
schema = [[c for c in line] for line in f.readlines()]
browse_schema(schema)