Day 10 completed
completed with the help of shapely python module other minors changes in day5
This commit is contained in:
parent
cdaa539b79
commit
64a270ee39
2
.gitignore
vendored
2
.gitignore
vendored
@ -162,3 +162,5 @@ cython_debug/
|
|||||||
**/*Zone.Identifier
|
**/*Zone.Identifier
|
||||||
**/input*
|
**/input*
|
||||||
cookies.txt
|
cookies.txt
|
||||||
|
|
||||||
|
.vscode/
|
173
day10/common.py
Normal file
173
day10/common.py
Normal 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)
|
5
day10/example_input_part1.txt
Normal file
5
day10/example_input_part1.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
7-F7-
|
||||||
|
.FJ|7
|
||||||
|
SJLL7
|
||||||
|
|F--J
|
||||||
|
LJ.LJ
|
9
day10/example_input_part2.txt
Normal file
9
day10/example_input_part2.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
...........
|
||||||
|
.S-------7.
|
||||||
|
.|F-----7|.
|
||||||
|
.||.....||.
|
||||||
|
.||.....||.
|
||||||
|
.|L-7.F-J|.
|
||||||
|
.|..|.|..|.
|
||||||
|
.L--J.L--J.
|
||||||
|
...........
|
34
day10/part1.py
Executable file
34
day10/part1.py
Executable 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
27
day10/part2.py
Executable 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
32
day10/test.py
Executable 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)
|
@ -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
|
@ -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
|
||||||
|
@ -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
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
shapely>=2.0.2
|
Loading…
x
Reference in New Issue
Block a user