From 81079f201913173f1adc8db8df8f393199d25ed8 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:16:45 +0200 Subject: [PATCH 01/16] Create solution file for part 1 --- puzzles/solutions/2022/d09/p1.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 puzzles/solutions/2022/d09/p1.py diff --git a/puzzles/solutions/2022/d09/p1.py b/puzzles/solutions/2022/d09/p1.py new file mode 100644 index 00000000..c3bbf496 --- /dev/null +++ b/puzzles/solutions/2022/d09/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 91572fa2db1d48e3b6810feeeba1a7f4bde48e30 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:22:19 +0200 Subject: [PATCH 02/16] Add constants module --- puzzles/solutions/2022/d09/consts.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 puzzles/solutions/2022/d09/consts.py diff --git a/puzzles/solutions/2022/d09/consts.py b/puzzles/solutions/2022/d09/consts.py new file mode 100644 index 00000000..e69de29b From 3fea21d1df905d42c451968d7e0e0b4fa66146ef Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:27:03 +0200 Subject: [PATCH 03/16] Add mapping between direction to step --- puzzles/solutions/2022/d09/consts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/puzzles/solutions/2022/d09/consts.py b/puzzles/solutions/2022/d09/consts.py index e69de29b..5a7e58f5 100644 --- a/puzzles/solutions/2022/d09/consts.py +++ b/puzzles/solutions/2022/d09/consts.py @@ -0,0 +1 @@ +DIRECTION_TO_STEP = {"R": (1, 0), "U": (0, 1), "L": (-1, 0), "D": (0, -1)} From c6e7c1e1059e2eafe1e60108cfdab75aa113082d Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:21:15 +0200 Subject: [PATCH 04/16] Add function that creates a sequence of steps from the input --- puzzles/solutions/2022/d09/p1.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/puzzles/solutions/2022/d09/p1.py b/puzzles/solutions/2022/d09/p1.py index c3bbf496..4478736b 100644 --- a/puzzles/solutions/2022/d09/p1.py +++ b/puzzles/solutions/2022/d09/p1.py @@ -1,4 +1,21 @@ import sys +from typing import Iterator + +import consts + + +def get_steps(input_text: str) -> Iterator[tuple[int, int]]: + """ + :param input_text: puzzle input + :return: sequence of steps, one by one + """ + steps = [] + for step_count in input_text.splitlines(): + direction, count = step_count.split() + step = consts.DIRECTION_TO_STEP[direction] + count = int(count) + yield from count * [step] + return tuple(steps) def get_answer(input_text: str): From 8dec2f08ec87b3aff96372850c55aa631b4f8c55 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:28:54 +0200 Subject: [PATCH 05/16] Create module for `Knot` class --- puzzles/solutions/2022/d09/knot.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 puzzles/solutions/2022/d09/knot.py diff --git a/puzzles/solutions/2022/d09/knot.py b/puzzles/solutions/2022/d09/knot.py new file mode 100644 index 00000000..e69de29b From 334b0791ecf9c0c17b0297056fc6b5c10ba35392 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:29:30 +0200 Subject: [PATCH 06/16] Create `Knot` class --- puzzles/solutions/2022/d09/knot.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/puzzles/solutions/2022/d09/knot.py b/puzzles/solutions/2022/d09/knot.py index e69de29b..599e4739 100644 --- a/puzzles/solutions/2022/d09/knot.py +++ b/puzzles/solutions/2022/d09/knot.py @@ -0,0 +1,6 @@ +import dataclasses + + +@dataclasses.dataclass +class Knot: + """A knot on a rope which can move.""" From 67e9ab821cb461c64935a4bd5b5f9748a6a7b3c7 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:30:02 +0200 Subject: [PATCH 07/16] Add "column" field to `Step` class --- puzzles/solutions/2022/d09/knot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/puzzles/solutions/2022/d09/knot.py b/puzzles/solutions/2022/d09/knot.py index 599e4739..3f0fdf1e 100644 --- a/puzzles/solutions/2022/d09/knot.py +++ b/puzzles/solutions/2022/d09/knot.py @@ -4,3 +4,6 @@ @dataclasses.dataclass class Knot: """A knot on a rope which can move.""" + + row: int + column: int From e20b6844fd0dea14b71b46fc8aff51974a500dd3 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:32:10 +0200 Subject: [PATCH 08/16] Add method that moves a knot --- puzzles/solutions/2022/d09/knot.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/puzzles/solutions/2022/d09/knot.py b/puzzles/solutions/2022/d09/knot.py index 3f0fdf1e..3a315c5f 100644 --- a/puzzles/solutions/2022/d09/knot.py +++ b/puzzles/solutions/2022/d09/knot.py @@ -7,3 +7,12 @@ class Knot: row: int column: int + + def move(self, step: tuple[int, int]) -> None: + """ + Move the knot by the given step. + :param step: pair of (row, column) movement + """ + x, y = step + self.row += x + self.column += y From 840d54066187e56fb274bbfcc57a4cb6a8e9bf2b Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 24 Dec 2022 15:49:07 +0200 Subject: [PATCH 09/16] Add constant for distance between knots to be "adjacent" to each other --- puzzles/solutions/2022/d09/consts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/puzzles/solutions/2022/d09/consts.py b/puzzles/solutions/2022/d09/consts.py index 5a7e58f5..dbd8a2f7 100644 --- a/puzzles/solutions/2022/d09/consts.py +++ b/puzzles/solutions/2022/d09/consts.py @@ -1 +1,2 @@ DIRECTION_TO_STEP = {"R": (1, 0), "U": (0, 1), "L": (-1, 0), "D": (0, -1)} +ADJACENT_DISTANCE = 1 From cf72e9242aa19d3700f04a7f9abbb26c2f5ab568 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:39:03 +0200 Subject: [PATCH 10/16] Add constant for distance between knots to be "far" from each other --- puzzles/solutions/2022/d09/consts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/puzzles/solutions/2022/d09/consts.py b/puzzles/solutions/2022/d09/consts.py index dbd8a2f7..7492e5b5 100644 --- a/puzzles/solutions/2022/d09/consts.py +++ b/puzzles/solutions/2022/d09/consts.py @@ -1,2 +1,3 @@ DIRECTION_TO_STEP = {"R": (1, 0), "U": (0, 1), "L": (-1, 0), "D": (0, -1)} ADJACENT_DISTANCE = 1 +FAR_DISTANCE = 2 From 01f7938e2dfdd313ecf9149b76dcb4d1e997cff3 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Wed, 21 Dec 2022 17:40:45 +0200 Subject: [PATCH 11/16] Add method that returns whether a knot and another knot are touching --- puzzles/solutions/2022/d09/knot.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/puzzles/solutions/2022/d09/knot.py b/puzzles/solutions/2022/d09/knot.py index 3a315c5f..51696fc9 100644 --- a/puzzles/solutions/2022/d09/knot.py +++ b/puzzles/solutions/2022/d09/knot.py @@ -1,5 +1,9 @@ import dataclasses +from typing import Self + +import consts + @dataclasses.dataclass class Knot: @@ -16,3 +20,13 @@ def move(self, step: tuple[int, int]) -> None: x, y = step self.row += x self.column += y + + def is_touching(self, other: Self) -> bool: + """ + :param other: other knot + :return: whether this knot and the other knot are touching + """ + return ( + abs(self.row - other.row) < consts.FAR_DISTANCE + and abs(self.column - other.column) < consts.FAR_DISTANCE + ) From 697a6b982e3b9f00790b1c064a864cbab68bbe76 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Fri, 23 Dec 2022 16:17:52 +0200 Subject: [PATCH 12/16] Add method that moves a knot to be touching another knot --- puzzles/solutions/2022/d09/knot.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/puzzles/solutions/2022/d09/knot.py b/puzzles/solutions/2022/d09/knot.py index 51696fc9..b2059782 100644 --- a/puzzles/solutions/2022/d09/knot.py +++ b/puzzles/solutions/2022/d09/knot.py @@ -1,4 +1,5 @@ import dataclasses +import math from typing import Self @@ -30,3 +31,27 @@ def is_touching(self, other: Self) -> bool: abs(self.row - other.row) < consts.FAR_DISTANCE and abs(self.column - other.column) < consts.FAR_DISTANCE ) + + def move_to_other(self, other: Self) -> None: + """ + Move this knot, so it will be touching the other knot. + :param other: other knot to move towards. + """ + if self.is_touching(other): + return + + row_distance = self.row - other.row + column_distance = self.column - other.column + + # If the distance is "far", we want to move the knot, so it will be on the same row/column, but not overlapping, + # so the distance to move should be 1. + # If the distance is "adjacent", it means we want to move diagonally (remember -- the knots are not touching, + # otherwise, we would return immediately at the beginning of this method), so the distance to move + # stays 1, too. + # If the distance is 0, no movement should occur, the condition is falsy, so the distance to move stays 0. + if abs(row_distance) == consts.FAR_DISTANCE: + row_distance = math.copysign(consts.ADJACENT_DISTANCE, row_distance) + if abs(column_distance) == consts.FAR_DISTANCE: + column_distance = math.copysign(consts.ADJACENT_DISTANCE, column_distance) + + self.move((-int(row_distance), -int(column_distance))) From a6d6e00bdd9c4406785477b99e94b66b64298040 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 24 Dec 2022 20:11:13 +0200 Subject: [PATCH 13/16] Add function that simulates a rope movement with a given amount of knots --- puzzles/solutions/2022/d09/p1.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/puzzles/solutions/2022/d09/p1.py b/puzzles/solutions/2022/d09/p1.py index 4478736b..04ab0edd 100644 --- a/puzzles/solutions/2022/d09/p1.py +++ b/puzzles/solutions/2022/d09/p1.py @@ -18,6 +18,27 @@ def get_steps(input_text: str) -> Iterator[tuple[int, int]]: return tuple(steps) +def get_visited_positions_amount( + steps: Iterator[tuple[int, int]], knots_amount: int +) -> int: + """ + :param steps: sequence of steps, one by one + :param knots_amount: amount of knots in the rope + :return: number of positions the tail of the rope visits at least once + """ + knots = [Knot(0, 0) for _ in range(knots_amount)] + head = knots[0] + tail = knots[-1] + visited_positions = set() + for step in steps: + head.move(step) + for index in range(1, knots_amount): + # Move each knot according to the knot it follows. + knots[index].move_to_other(knots[index - 1]) + visited_positions.add((tail.row, tail.column)) + return len(visited_positions) + + def get_answer(input_text: str): raise NotImplementedError From 2d308a68e52dfef2f16511ba036640d0e5bed2fb Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 24 Dec 2022 20:12:31 +0200 Subject: [PATCH 14/16] Add a function that returns number of positions tail of the rope visits This is for a rope with two knots. --- puzzles/solutions/2022/d09/p1.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/puzzles/solutions/2022/d09/p1.py b/puzzles/solutions/2022/d09/p1.py index 04ab0edd..206fec78 100644 --- a/puzzles/solutions/2022/d09/p1.py +++ b/puzzles/solutions/2022/d09/p1.py @@ -1,6 +1,7 @@ import sys from typing import Iterator +from knot import Knot import consts @@ -40,7 +41,9 @@ def get_visited_positions_amount( def get_answer(input_text: str): - raise NotImplementedError + """Return the number of positions the tail of the rope visits at least once, when the rope has total 2 knots.""" + steps = get_steps(input_text) + return get_visited_positions_amount(steps, 2) if __name__ == "__main__": From 29c2e56d187cff986bcb9c6546b10d48df38374d Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 24 Dec 2022 20:13:14 +0200 Subject: [PATCH 15/16] Create solution file for part 2 --- puzzles/solutions/2022/d09/p2.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 puzzles/solutions/2022/d09/p2.py diff --git a/puzzles/solutions/2022/d09/p2.py b/puzzles/solutions/2022/d09/p2.py new file mode 100644 index 00000000..c3bbf496 --- /dev/null +++ b/puzzles/solutions/2022/d09/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 66aaa28f5b172b1a8f9d8afa95aeeaac4999afa2 Mon Sep 17 00:00:00 2001 From: Dan Katzuv Date: Sat, 24 Dec 2022 20:14:38 +0200 Subject: [PATCH 16/16] Add a function that returns number of positions tail of the rope visits This is for a rope with ten knots. --- puzzles/solutions/2022/d09/p2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/puzzles/solutions/2022/d09/p2.py b/puzzles/solutions/2022/d09/p2.py index c3bbf496..e34bc55d 100644 --- a/puzzles/solutions/2022/d09/p2.py +++ b/puzzles/solutions/2022/d09/p2.py @@ -1,8 +1,12 @@ import sys +import p1 + def get_answer(input_text: str): - raise NotImplementedError + """Return the number of positions the tail of the rope visits at least once, when the rope has total 10 knots.""" + steps = p1.get_steps(input_text) + return p1.get_visited_positions_amount(steps, 10) if __name__ == "__main__":