From b76fe698fb3627c6425c5b4728f8d1f77c316132 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Fri, 8 Dec 2023 21:48:20 +0200 Subject: [PATCH 01/14] Create solution file for part 1 --- puzzles/solutions/2023/d03/p1.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 puzzles/solutions/2023/d03/p1.py diff --git a/puzzles/solutions/2023/d03/p1.py b/puzzles/solutions/2023/d03/p1.py new file mode 100644 index 00000000..c3bbf496 --- /dev/null +++ b/puzzles/solutions/2023/d03/p1.py @@ -0,0 +1,12 @@ +import sys + + +def get_answer(input_text: str): + raise NotImplementedError + + +if __name__ == "__main__": + try: + print(get_answer(sys.argv[1])) + except IndexError: + pass # Don't crash if no input was passed through command line arguments. From 237270d43b6508d23fbc6940a29c5fecb7a1d5bf Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 9 Dec 2023 00:06:39 +0200 Subject: [PATCH 02/14] Create module for the `SchematicParser` class --- puzzles/solutions/2023/d03/schematic_parser.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 puzzles/solutions/2023/d03/schematic_parser.py diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py new file mode 100644 index 00000000..e69de29b From 591e42ba7aac169643e10168b10f013e35cffd19 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 9 Dec 2023 00:08:56 +0200 Subject: [PATCH 03/14] Create `SchematicParser` class --- puzzles/solutions/2023/d03/schematic_parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index e69de29b..e1316053 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -0,0 +1,2 @@ +class SchematicParser: + """Class that parsers the engine schematic.""" From a1a42f2d7c1603c11f1d2dea549e1f22f4fd8b2a Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 9 Dec 2023 00:09:59 +0200 Subject: [PATCH 04/14] Add constructor to `SchematicParser` class --- puzzles/solutions/2023/d03/schematic_parser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index e1316053..5022db36 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -1,2 +1,9 @@ class SchematicParser: """Class that parsers the engine schematic.""" + + def __init__(self, engine_schematic: str): + self._schematic = engine_schematic.splitlines() + self._schematic_length = len(self._schematic) + self._schematic_width = len(self._schematic[0]) + + self._numbers = [] From 9ee5c0257527933e2b0ea977af03d1914169cea0 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 9 Dec 2023 00:19:46 +0200 Subject: [PATCH 05/14] Add method that checks whether character is adjacent to other characters --- .../solutions/2023/d03/schematic_parser.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index 5022db36..d7918928 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -1,3 +1,7 @@ +import itertools +from typing import Iterable + + class SchematicParser: """Class that parsers the engine schematic.""" @@ -7,3 +11,35 @@ def __init__(self, engine_schematic: str): self._schematic_width = len(self._schematic[0]) self._numbers = [] + + def _is_character_adjacent_to_character( + self, + character_row: int, + character_column: int, + characters: Iterable[str], + should_match: bool, + ) -> bool: + """ + :param character_row: row of the checked character + :param character_column: column of the checked character + :param characters: characters to check against + :param should_match: should the character be adjacent one of the given characters or not + :return: whether the character is (not) adjacent to the given characters + """ + upper_row = max(0, character_row - 1) + lower_row = min(self._schematic_length - 1, character_row + 1) + left_column = max(0, character_column - 1) + right_column = min(self._schematic_width - 1, character_column + 1) + + neighbors = list( + itertools.product( + range(upper_row, lower_row + 1), range(left_column, right_column + 1) + ) + ) + neighbors.remove((character_row, character_column)) + for row, column in neighbors: + character = self._schematic[row][column] + if (character in characters) == should_match: + return True + + return False From 30696618449630c3c32a2a610db345884817225f Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:00:45 +0200 Subject: [PATCH 06/14] Add method that adds the given number to the numbers list --- puzzles/solutions/2023/d03/schematic_parser.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index d7918928..d10bf6c7 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -43,3 +43,12 @@ def _is_character_adjacent_to_character( return True return False + + def _add_number(self, row: int, column: int, digits: list[str]) -> None: + if not digits: + return + + number = int("".join(digits)) + first_column = column - len(digits) + positions = tuple(zip(itertools.repeat(row), range(first_column, column))) + self._numbers.append((number, positions)) From 542e8760999032d823091803a8a8fd6a7945ddb8 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:01:27 +0200 Subject: [PATCH 07/14] Add method that finds all the parts numbers --- .../solutions/2023/d03/schematic_parser.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index d10bf6c7..e8ff8eaf 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -1,4 +1,5 @@ import itertools +import string from typing import Iterable @@ -52,3 +53,26 @@ def _add_number(self, row: int, column: int, digits: list[str]) -> None: first_column = column - len(digits) positions = tuple(zip(itertools.repeat(row), range(first_column, column))) self._numbers.append((number, positions)) + + def find_part_numbers(self) -> list[int]: + current_digits = [] + is_adjacent_to_symbol = False + + for row, column in itertools.product( + range(self._schematic_length), range(self._schematic_width) + ): + character = self._schematic[row][column] + if character.isdigit(): + current_digits.append(character) + is_adjacent_to_symbol |= self._is_character_adjacent_to_character( + row, column, string.digits + ".", False + ) + # Avoid edge case where there is a part number at the end of one line and another on the + # start of the following line -- we want to treat them as two different part numbers. + if not character.isdigit() or column == self._schematic_width - 1: + if is_adjacent_to_symbol: + self._add_number(row, column, current_digits) + current_digits = [] + is_adjacent_to_symbol = False + + return [number for number, digits_positions in self._numbers] From 6dc00f0f0fe76e9501eb815fa42e22f7ad721e78 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:04:14 +0200 Subject: [PATCH 08/14] Add function that returns sum of part numbers in the engine schematic --- puzzles/solutions/2023/d03/p1.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/puzzles/solutions/2023/d03/p1.py b/puzzles/solutions/2023/d03/p1.py index c3bbf496..9fa518cd 100644 --- a/puzzles/solutions/2023/d03/p1.py +++ b/puzzles/solutions/2023/d03/p1.py @@ -1,8 +1,13 @@ import sys +from schematic_parser import SchematicParser + def get_answer(input_text: str): - raise NotImplementedError + """Return the sum of all part numbers in the engine schematic.""" + schematic_parser = SchematicParser(input_text) + part_numbers = schematic_parser.find_part_numbers() + return sum(part_numbers) if __name__ == "__main__": From 14a955aeda86035b4ae4ae60b917120f0cf1dc8b Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:34:02 +0200 Subject: [PATCH 09/14] Create solution file for part 2 --- puzzles/solutions/2023/d03/p2.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 puzzles/solutions/2023/d03/p2.py diff --git a/puzzles/solutions/2023/d03/p2.py b/puzzles/solutions/2023/d03/p2.py new file mode 100644 index 00000000..c3bbf496 --- /dev/null +++ b/puzzles/solutions/2023/d03/p2.py @@ -0,0 +1,12 @@ +import sys + + +def get_answer(input_text: str): + raise NotImplementedError + + +if __name__ == "__main__": + try: + print(get_answer(sys.argv[1])) + except IndexError: + pass # Don't crash if no input was passed through command line arguments. From 7dfbd77c3c48f075e1dc5dac643d6ffe63d4b6d8 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:32:17 +0200 Subject: [PATCH 10/14] Add gears positions list to `SchematicParser` constructor --- puzzles/solutions/2023/d03/schematic_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index e8ff8eaf..f39b6940 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -12,6 +12,7 @@ def __init__(self, engine_schematic: str): self._schematic_width = len(self._schematic[0]) self._numbers = [] + self._gears_positions = [] def _is_character_adjacent_to_character( self, From 94cdc82400b8073d02f0152dc0a9ac5c08865f74 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:32:43 +0200 Subject: [PATCH 11/14] Add methods that populates the `_gears_positions` list --- puzzles/solutions/2023/d03/schematic_parser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index f39b6940..71cb600b 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -77,3 +77,10 @@ def find_part_numbers(self) -> list[int]: is_adjacent_to_symbol = False return [number for number, digits_positions in self._numbers] + + def find_gears(self): + for row, column in itertools.product( + range(self._schematic_length), range(self._schematic_width) + ): + if self._schematic[row][column] == "*": + self._gears_positions.append((row, column)) From 831dd550788b0022d59b4ef60e6e69db8a443b94 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:33:35 +0200 Subject: [PATCH 12/14] Add methods that returns whether given position and digits are adjacent --- puzzles/solutions/2023/d03/schematic_parser.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index 71cb600b..a0b957dc 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -84,3 +84,13 @@ def find_gears(self): ): if self._schematic[row][column] == "*": self._gears_positions.append((row, column)) + + @staticmethod + def _is_number_adjacent( + position: tuple[int, int], digits_positions: Iterable[tuple[int, int]] + ) -> bool: + position_row, position_column = position + return any( + abs(row - position_row) <= 1 and abs(column - position_column) <= 1 + for row, column in digits_positions + ) From 2bfdcea9f8bd10da1c515e62c7d620af6c702517 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:33:51 +0200 Subject: [PATCH 13/14] Add methods that returns gear ratios sum --- puzzles/solutions/2023/d03/schematic_parser.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/puzzles/solutions/2023/d03/schematic_parser.py b/puzzles/solutions/2023/d03/schematic_parser.py index a0b957dc..5fe69e36 100644 --- a/puzzles/solutions/2023/d03/schematic_parser.py +++ b/puzzles/solutions/2023/d03/schematic_parser.py @@ -94,3 +94,16 @@ def _is_number_adjacent( abs(row - position_row) <= 1 and abs(column - position_column) <= 1 for row, column in digits_positions ) + + def calculate_gear_ratios_sum(self): + gear_ratios_sum = 0 + for gear_position in self._gears_positions: + adjacent_numbers = [ + number + for number, digits_positions in self._numbers + if self._is_number_adjacent(gear_position, digits_positions) + ] + if len(adjacent_numbers) == 2: + gear_ratio = adjacent_numbers[0] * adjacent_numbers[1] + gear_ratios_sum += gear_ratio + return gear_ratios_sum From 5eda897d389a28e52d7b577b2605a711a3ac023f Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sun, 10 Dec 2023 00:35:58 +0200 Subject: [PATCH 14/14] Add function that returns sum of all gear ratios in the engine schematic --- puzzles/solutions/2023/d03/p2.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/puzzles/solutions/2023/d03/p2.py b/puzzles/solutions/2023/d03/p2.py index c3bbf496..4f490e1d 100644 --- a/puzzles/solutions/2023/d03/p2.py +++ b/puzzles/solutions/2023/d03/p2.py @@ -1,8 +1,14 @@ import sys +from schematic_parser import SchematicParser + def get_answer(input_text: str): - raise NotImplementedError + """Return the sum of all gear ratios in the engine schematic.""" + schematic_parser = SchematicParser(input_text) + schematic_parser.find_part_numbers() + schematic_parser.find_gears() + return schematic_parser.calculate_gear_ratios_sum() if __name__ == "__main__":