From 64a270ee398b0b69e5914aa81a981105422eb32d Mon Sep 17 00:00:00 2001 From: Fedaya Date: Sun, 10 Dec 2023 17:34:02 +0100 Subject: [PATCH] Day 10 completed completed with the help of shapely python module other minors changes in day5 --- .gitignore | 2 + day10/common.py | 173 ++++++++++++++++++++++++++++++++++ day10/example_input_part1.txt | 5 + day10/example_input_part2.txt | 9 ++ day10/part1.py | 34 +++++++ day10/part2.py | 27 ++++++ day10/test.py | 32 +++++++ day5/{data.py => common.py} | 41 +++++--- day5/part1.py | 2 +- day5/part2_time_hog.py | 2 +- requirements.txt | 1 + 11 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 day10/common.py create mode 100644 day10/example_input_part1.txt create mode 100644 day10/example_input_part2.txt create mode 100755 day10/part1.py create mode 100755 day10/part2.py create mode 100755 day10/test.py rename day5/{data.py => common.py} (52%) create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index fea2439..9ec513c 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,5 @@ cython_debug/ **/*Zone.Identifier **/input* cookies.txt + +.vscode/ \ No newline at end of file diff --git a/day10/common.py b/day10/common.py new file mode 100644 index 0000000..9ca8f29 --- /dev/null +++ b/day10/common.py @@ -0,0 +1,173 @@ +from __future__ import annotations +from dataclasses import dataclass +from collections import defaultdict +import logging +from typing import NamedTuple, TypeGuard +from enum import Enum +import sys + + +class Point(NamedTuple): + x: int + y: int + c: str + + def add_delta(self, delta: PointDelta, labyrinth: Labyrinth) -> Point | None: + if (0 <= self.x + delta.x < len(labyrinth[0])) and ( + 0 <= self.y + delta.y < len(labyrinth) + ): + return labyrinth[self.y + delta.y][self.x + delta.x] + return None + + +class PointDelta(NamedTuple): + x: int + y: int + + +class Deltas(Enum): + UP = PointDelta(0, -1) + DOWN = PointDelta(0, 1) + LEFT = PointDelta(-1, 0) + RIGHT = PointDelta(1, 0) + NONE = PointDelta(0, 0) + + +NO_MOVEMENT = (Deltas.NONE, Deltas.NONE) + + +PIPES = defaultdict( + lambda: NO_MOVEMENT, + [ + ("|", (Deltas.UP, Deltas.DOWN)), + ("-", (Deltas.LEFT, Deltas.RIGHT)), + ("L", (Deltas.UP, Deltas.RIGHT)), + ("J", (Deltas.UP, Deltas.LEFT)), + ("7", (Deltas.DOWN, Deltas.LEFT)), + ("F", (Deltas.DOWN, Deltas.RIGHT)), + ], +) + + +@dataclass(init=False) +class Labyrinth: + _data: tuple[tuple[Point, ...], ...] # stored as [Y][X] + start: Point + first_points: tuple[Point, ...] + + def __init__( + self, + data: tuple[tuple[Point, ...], ...], + start: Point, + first_points: tuple[Point, ...] | None = None, + ): + self._data = data + self.start = start + self.first_points = ( + self._find_first_points() if first_points is None else first_points + ) + logging.debug(("start", start)) + logging.debug(("first_points", self.first_points)) + + def __getitem__(self, key: int) -> tuple[Point, ...]: + return self._data[key] + + def __len__(self): + return len(self._data) + + def __iter__(self): + return iter(self._data) + + def _find_first_points(self) -> tuple[Point, ...]: + up = self.start.add_delta(Deltas.UP.value, self) + up = up if up is not None and Deltas.DOWN in PIPES[up.c] else None + down = self.start.add_delta(Deltas.DOWN.value, self) + down = down if down is not None and Deltas.UP in PIPES[down.c] else None + left = self.start.add_delta(Deltas.LEFT.value, self) + left = left if left is not None and Deltas.RIGHT in PIPES[left.c] else None + right = self.start.add_delta(Deltas.RIGHT.value, self) + right = right if right is not None and Deltas.LEFT in PIPES[right.c] else None + + def point_is_not_none(n: Point | None) -> TypeGuard[Point]: # makes mypy happy + return n is not None + + return tuple(filter(point_is_not_none, [up, down, left, right])) + + def get_labyrinth(self): + return self._data + + def get_path(self) -> list[Point]: + path = [self.start] + previous_point = self.start + current_point = self.first_points[0] + while current_point != self.start: + path.append(current_point) + new_point = movement(current_point, previous_point, self) + previous_point = current_point + current_point = new_point + logging.debug(current_point) + path.append(current_point) + return path + + def get_empty_points(self) -> list[Point]: + empty_points = [] + for line in self._data: + for point in line: + if point.c == ".": + empty_points.append(point) + return empty_points + + def clean(self) -> Labyrinth: + path = self.get_path() + new_data = [] + for y, line in enumerate(self._data): + new_line = [] + for x, point in enumerate(line): + if point not in path: + new_line.append(Point(x, y, ".")) + else: + new_line.append(point) + new_data.append(tuple(new_line)) + return Labyrinth(new_data, self.start, self.first_points) + + def show(self, descriptor=sys.stdout) -> None: + for line in self._data: + linestr = "".join(map(lambda p: p.c, line)) + "\n" + descriptor.write(linestr) + + +def movement( + current_point: Point, previous_point: Point, labyrinth: Labyrinth +) -> Point: + movement_type = PIPES[current_point.c] + if movement_type == NO_MOVEMENT: + return current_point + movement_value = tuple(x.value for x in movement_type) + diff = PointDelta( + previous_point.x - current_point.x, previous_point.y - current_point.y + ) + if diff not in movement_value: + return current_point + idx = 0 if movement_value.index(diff) == 1 else 1 + return labyrinth[current_point.y + movement_value[idx].y][ + current_point.x + movement_value[idx].x + ] + + +def parse(input_file: str) -> Labyrinth: + logging.debug(f"parsing {input_file}") + array = [] + start_point = Point(-1, -1, "") + line_no = 0 + with open(input_file, "r", encoding="UTF-8") as input_handle: + while line := input_handle.readline().rstrip("\n").rstrip(): + ar_line = [] + for col_no, char in enumerate(line): + new_point = Point(col_no, line_no, char) + logging.debug(f"new point: {new_point}") + ar_line.append(new_point) + if char == "S": + start_point = new_point + array.append(tuple(ar_line)) + line_no += 1 + return Labyrinth(data=tuple(array), start=start_point) diff --git a/day10/example_input_part1.txt b/day10/example_input_part1.txt new file mode 100644 index 0000000..1ddc980 --- /dev/null +++ b/day10/example_input_part1.txt @@ -0,0 +1,5 @@ +7-F7- +.FJ|7 +SJLL7 +|F--J +LJ.LJ \ No newline at end of file diff --git a/day10/example_input_part2.txt b/day10/example_input_part2.txt new file mode 100644 index 0000000..6933a28 --- /dev/null +++ b/day10/example_input_part2.txt @@ -0,0 +1,9 @@ +........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +........... \ No newline at end of file diff --git a/day10/part1.py b/day10/part1.py new file mode 100755 index 0000000..e278209 --- /dev/null +++ b/day10/part1.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3.11 +from collections.abc import Iterable +from common import * + + +def all_equals(points: list[Point]) -> bool: + first = points[0] + logging.debug(f"testing {points} with value {first}") + return all(map(lambda x: x == first, points)) + + +def farthest_length(labyrinth: Labyrinth) -> int: + length = 1 + current_vectors = list( + ((labyrinth.start, point) for point in labyrinth.first_points) + ) + logging.debug(f"first vectors: {current_vectors}") + current_points = list(vector[1] for vector in current_vectors) + logging.debug(current_points) + while not all_equals(current_points): + new_vectors = [] + new_points = [] + for vector in current_vectors: + new_vector = (vector[1], movement(vector[1], vector[0], labyrinth)) + new_vectors.append(new_vector) + new_points.append(new_vector[1]) + current_vectors = new_vectors + current_points = new_points + length += 1 + return length + + +if __name__ == "__main__": + print(farthest_length(parse("input.txt"))) diff --git a/day10/part2.py b/day10/part2.py new file mode 100755 index 0000000..62d74db --- /dev/null +++ b/day10/part2.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3.11 + +from common import * +import shapely + + +def inside_points(labyrinth: Labyrinth) -> list[Point]: + labyrinth = labyrinth.clean() + path = labyrinth.get_path() + empty_points = labyrinth.get_empty_points() + logging.debug("let's shapely that") + shp_path = [] + for point in path: + shp_path.append(shapely.Point(point.x, point.y)) + polygon = shapely.Polygon(shp_path) + inside_points = [] + for point in empty_points: + current = shapely.Point(point.x, point.y) + if shapely.contains(polygon, current): + inside_points.append(point) + + return inside_points + + +if __name__ == "__main__": + labyrinth = parse("input.txt") + print(len(inside_points(labyrinth))) diff --git a/day10/test.py b/day10/test.py new file mode 100755 index 0000000..5f4bf8d --- /dev/null +++ b/day10/test.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3.11 +import logging +from unittest import TestCase, main +from part1 import farthest_length +from part2 import inside_points +from common import parse, Point + + +class Day10Tests(TestCase): + def test_parsing(self): + labyrinth = parse("example_input_part1.txt") + self.assertEqual(labyrinth.start, Point(0, 2, "S")) + self.assertEqual(labyrinth.first_points, (Point(0, 3, "|"), Point(1, 2, "J"))) + + def test_part1(self): + self.assertEqual(8, farthest_length(parse("example_input_part1.txt"))) + + def test_part2(self): + self.assertEqual( + [ + Point(x=2, y=6, c="."), + Point(x=3, y=6, c="."), + Point(x=7, y=6, c="."), + Point(x=8, y=6, c="."), + ], + inside_points(parse("example_input_part2.txt")), + ) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + main(verbosity=2) diff --git a/day5/data.py b/day5/common.py similarity index 52% rename from day5/data.py rename to day5/common.py index ff04a4a..4ff1a62 100644 --- a/day5/data.py +++ b/day5/common.py @@ -1,6 +1,7 @@ import re from dataclasses import dataclass -from typing import Optional, Dict, List, Tuple +from typing import Optional, Dict, List, Tuple, OrderedDict as OrderedDictType +from collections import OrderedDict @dataclass @@ -18,17 +19,17 @@ class AlmanacMap: return self.destination + (source - self.source) -def extract(input_file: str) -> Tuple[List[int], Dict[str, List[AlmanacMap]]]: +def extract(input_file: str) -> Tuple[List[int], OrderedDictType[str, List[AlmanacMap]]]: seeds = [] - maps = { - "seed-to-soil": [], - "soil-to-fertilizer": [], - "fertilizer-to-water": [], - "water-to-light": [], - "light-to-temperature": [], - "temperature-to-humidity": [], - "humidity-to-location": [], - } + maps = OrderedDict( + ("seed-to-soil", []), + ("soil-to-fertilizer", []), + ("fertilizer-to-water", []), + ("water-to-light", []), + ("light-to-temperature", []), + ("temperature-to-humidity", []), + ("humidity-to-location", []), + ) with open(input_file) as input: current_map = {} @@ -44,3 +45,21 @@ def extract(input_file: str) -> Tuple[List[int], Dict[str, List[AlmanacMap]]]: destination, source, length = match.group(1).split(" ") current_map.append(AlmanacMap(destination=int(destination), source=int(source), length=int(length))) return seeds, maps + + +def next_maps(a_map: AlmanacMap, map_type: str, maps: OrderedDictType[str, AlmanacMap]) -> List[AlmanacMap]: + mini = a_map.destination + maxi = a_map.destination + a_map.length + maps_next_level = list( + filter( + lambda m: m.destination <= maxi and (m.destination + m.length) >= mini, + maps[maps.keys().index(map_type) + 1], + ) + ) + return maps_next_level + + +def seed_to_location_map(maps): + seed_to_location = [] + for seed_group in maps["seed-to-soil"]: + return seed_to_location diff --git a/day5/part1.py b/day5/part1.py index 714dab5..d242ff1 100755 --- a/day5/part1.py +++ b/day5/part1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3.11 # Is killed on WSL2 after vmmem increase to 16GB -from data import extract +from day5.common import extract seeds, maps = extract("input.txt") lowest_location = None diff --git a/day5/part2_time_hog.py b/day5/part2_time_hog.py index bdcb7ee..3834f4a 100755 --- a/day5/part2_time_hog.py +++ b/day5/part2_time_hog.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3.11 # Is killed on WSL2 after vmmem increase to 16GB -from data import extract +from day5.common import extract import concurrent.futures def seed_to_location(seed, maps): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..83a4cf7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +shapely>=2.0.2 \ No newline at end of file