--- /dev/null
+467....114
+...*......
+..35..633.
+......#...
+617*......
+.....+.58.
+..592.....
+......755.
+...$.*....
+.664.598..
--- /dev/null
+#!/bin/env python3
+
+import sys
+import argparse
+from schematic import Schematic
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="AoC 2023 day 2")
+ parser.add_argument('filename', type=str, help="input path")
+ parser.add_argument('-d', '--debug', action='store_true')
+ options = parser.parse_args(args)
+
+ with open(options.filename, 'r') as input:
+ schematic = Schematic.fromstream(input)
+ print(f"part1: {sum(schematic.parts)}")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+from typing import List
+
+
+class Schematic:
+ def __init__(self, rows: List[str]):
+ self.rows = rows
+ self.span = len(rows[0])
+ self.parts = []
+ self.debug = True
+ self.update()
+
+ def update(self):
+ """Parse the input data and generate the list of parts"""
+ self.parts = []
+ for row in range(len(self.rows)):
+ part = {'digits': [], 'symbols':[]}
+ for col in range(self.span):
+ c = self.rows[row][col]
+ if c.isdigit():
+ part['digits'].append(c)
+ part['symbols'].append(self.get_neighbours(row, col))
+ else:
+ if part['digits']:
+ self._complete_part(part)
+ part = {'digits': [], 'symbols':[]}
+ if part['digits']:
+ self._complete_part(part)
+ part = {'digits': [], 'symbols':[]}
+
+ def _complete_part(self, part:dict):
+ value = int(''.join(part['digits']))
+ valid = bool(''.join(part['symbols']))
+ if self.debug:
+ print(value, valid, ''.join(part['symbols']))
+ if valid:
+ self.parts.append(value)
+
+ def get_neighbours(self, row: int, col: int) -> bool:
+ result = ''
+ for r in [row - 1, row, row + 1]:
+ if r >= 0 and r < len(self.rows):
+ for c in [col - 1, col, col + 1]:
+ if c >= 0 and c < self.span:
+ t = self.rows[r][c]
+ if not (t.isdigit() or t == '.'):
+ result = result + t
+ return result
+
+ @staticmethod
+ def fromstream(stream) -> 'Schematic':
+ return Schematic([x.strip() for x in stream.readlines() if x.strip()])
--- /dev/null
+import unittest
+import os
+import sys
+from io import StringIO
+from schematic import Schematic
+
+
+class TestPart1(unittest.TestCase):
+ def test_schematic_sum(self):
+ with open('data/test_input') as input:
+ schematic = Schematic.fromstream(input)
+ self.assertSequenceEqual(
+ [467, 35, 633, 617, 592, 755, 664, 598],
+ schematic.parts)
+ self.assertEqual(4361, sum(schematic.parts))
+
+ def test_schematic_from_file2(self):
+ with open('data/test_input2') as input:
+ schematic = Schematic.fromstream(input)
+ self.assertSequenceEqual(
+ [467, 35, 633, 617, 592, 755, 664, 598],
+ schematic.parts)
+
+if __name__ == '__main__':
+ unittest.main()