Day 10 completed

completed with the help of shapely python module

other minors changes in day5
This commit is contained in:
Fedaya 2023-12-10 17:34:02 +01:00
parent cdaa539b79
commit 64a270ee39
11 changed files with 315 additions and 13 deletions

2
.gitignore vendored
View File

@ -162,3 +162,5 @@ cython_debug/
**/*Zone.Identifier **/*Zone.Identifier
**/input* **/input*
cookies.txt cookies.txt
.vscode/

173
day10/common.py Normal file
View File

@ -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)

View File

@ -0,0 +1,5 @@
7-F7-
.FJ|7
SJLL7
|F--J
LJ.LJ

View File

@ -0,0 +1,9 @@
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........

34
day10/part1.py Executable file
View File

@ -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")))

27
day10/part2.py Executable file
View File

@ -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)))

32
day10/test.py Executable file
View File

@ -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)

View File

@ -1,6 +1,7 @@
import re import re
from dataclasses import dataclass 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 @dataclass
@ -18,17 +19,17 @@ class AlmanacMap:
return self.destination + (source - self.source) 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 = [] seeds = []
maps = { maps = OrderedDict(
"seed-to-soil": [], ("seed-to-soil", []),
"soil-to-fertilizer": [], ("soil-to-fertilizer", []),
"fertilizer-to-water": [], ("fertilizer-to-water", []),
"water-to-light": [], ("water-to-light", []),
"light-to-temperature": [], ("light-to-temperature", []),
"temperature-to-humidity": [], ("temperature-to-humidity", []),
"humidity-to-location": [], ("humidity-to-location", []),
} )
with open(input_file) as input: with open(input_file) as input:
current_map = {} 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(" ") destination, source, length = match.group(1).split(" ")
current_map.append(AlmanacMap(destination=int(destination), source=int(source), length=int(length))) current_map.append(AlmanacMap(destination=int(destination), source=int(source), length=int(length)))
return seeds, maps 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

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3.11 #!/usr/bin/env python3.11
# Is killed on WSL2 after vmmem increase to 16GB # Is killed on WSL2 after vmmem increase to 16GB
from data import extract from day5.common import extract
seeds, maps = extract("input.txt") seeds, maps = extract("input.txt")
lowest_location = None lowest_location = None

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3.11 #!/usr/bin/env python3.11
# Is killed on WSL2 after vmmem increase to 16GB # Is killed on WSL2 after vmmem increase to 16GB
from data import extract from day5.common import extract
import concurrent.futures import concurrent.futures
def seed_to_location(seed, maps): def seed_to_location(seed, maps):

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
shapely>=2.0.2