Day 11 completed
This commit is contained in:
parent
64a270ee39
commit
ed55a50ec7
139
day11/common.py
Normal file
139
day11/common.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
from itertools import combinations
|
||||||
|
|
||||||
|
# from pprint import pformat
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
class Coordinate(NamedTuple):
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
c: str
|
||||||
|
|
||||||
|
def is_galaxy(self):
|
||||||
|
return self.c == "#"
|
||||||
|
|
||||||
|
def distance(
|
||||||
|
self, other: Coordinate, universe: Universe | None = None, factor: int = 1
|
||||||
|
) -> int:
|
||||||
|
current = self
|
||||||
|
movements = 0
|
||||||
|
while current != other:
|
||||||
|
if current.x < other.x:
|
||||||
|
movements += 1
|
||||||
|
current = Coordinate(current.x + 1, current.y, current.c)
|
||||||
|
elif current.x > other.x:
|
||||||
|
movements += 1
|
||||||
|
current = Coordinate(current.x - 1, current.y, current.c)
|
||||||
|
if universe is not None:
|
||||||
|
if current.x in universe.empty_cols:
|
||||||
|
movements += -1 + factor
|
||||||
|
|
||||||
|
if current.y < other.y:
|
||||||
|
movements += 1
|
||||||
|
current = Coordinate(current.x, current.y + 1, current.c)
|
||||||
|
elif current.y > other.y:
|
||||||
|
movements += 1
|
||||||
|
current = Coordinate(current.x, current.y - 1, current.c)
|
||||||
|
if universe is not None:
|
||||||
|
if current.y in universe.empty_lines:
|
||||||
|
movements += -1 + factor
|
||||||
|
|
||||||
|
logging.debug(f"distance between {self} and {other} = {movements}")
|
||||||
|
return movements
|
||||||
|
|
||||||
|
|
||||||
|
def pairs_of_stars(universe: Universe) -> list[tuple[Coordinate, Coordinate]]:
|
||||||
|
return list(combinations(universe.stars, 2))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class Universe:
|
||||||
|
_data: list[list[Coordinate]]
|
||||||
|
stars: list[Coordinate]
|
||||||
|
empty_lines: list[int] | None
|
||||||
|
empty_cols: list[int] | None
|
||||||
|
|
||||||
|
def __init__(self, data: list[list[Coordinate]], part: int = 1):
|
||||||
|
self._data = data
|
||||||
|
if part == 1:
|
||||||
|
self._expand_universe()
|
||||||
|
self.stars = []
|
||||||
|
stars = [filter(lambda c: c.is_galaxy(), line) for line in self._data]
|
||||||
|
for line in stars:
|
||||||
|
self.stars.extend(line)
|
||||||
|
if part == 2:
|
||||||
|
self.empty_cols = []
|
||||||
|
self.empty_lines = []
|
||||||
|
for x in range(len(self._data[0])):
|
||||||
|
if all(
|
||||||
|
map(lambda c: not c.is_galaxy(), [line[x] for line in self._data])
|
||||||
|
):
|
||||||
|
self.empty_cols.append(x)
|
||||||
|
for line in self._data:
|
||||||
|
if all(map(lambda c: not c.is_galaxy(), line)):
|
||||||
|
self.empty_lines.append(line[0].y)
|
||||||
|
|
||||||
|
def __getitem__(self, key: int) -> list[Coordinate]:
|
||||||
|
return self._data[key]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._data)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._data)
|
||||||
|
|
||||||
|
def _expand_universe(self) -> None:
|
||||||
|
# universe expansion on x
|
||||||
|
# first find columns with no galaxy
|
||||||
|
empty_cols: list[int] = []
|
||||||
|
for x in range(len(self._data[0])):
|
||||||
|
if all(map(lambda c: not c.is_galaxy(), [line[x] for line in self._data])):
|
||||||
|
empty_cols.append(x)
|
||||||
|
for y, line in enumerate(self._data):
|
||||||
|
x_offset = 0
|
||||||
|
new_line = []
|
||||||
|
for col in line:
|
||||||
|
if x_offset > 0:
|
||||||
|
new_col = Coordinate(col.x + x_offset, col.y, col.c)
|
||||||
|
new_line.append(new_col)
|
||||||
|
else:
|
||||||
|
new_line.append(col)
|
||||||
|
if col.x in empty_cols:
|
||||||
|
x_offset += 1
|
||||||
|
new_col = Coordinate(col.x + x_offset, col.y, col.c)
|
||||||
|
new_line.append(new_col)
|
||||||
|
self._data[y] = new_line
|
||||||
|
# universe expansion on y
|
||||||
|
new_universe = []
|
||||||
|
y_offset = 0
|
||||||
|
for y, line in enumerate(self._data):
|
||||||
|
if y_offset > 0:
|
||||||
|
new_line = [Coordinate(o.x, o.y + y_offset, o.c) for o in line]
|
||||||
|
new_universe.append(new_line)
|
||||||
|
else:
|
||||||
|
new_universe.append(line)
|
||||||
|
if all(map(lambda c: not c.is_galaxy(), line)):
|
||||||
|
y_offset += 1
|
||||||
|
new_line = [Coordinate(o.x, o.y + y_offset, o.c) for o in line]
|
||||||
|
new_universe.append(new_line)
|
||||||
|
self._data = new_universe
|
||||||
|
# logging.debug(pformat(self._data))
|
||||||
|
|
||||||
|
def get_map(self):
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
|
||||||
|
def parse(input_file: str, part: int = 1) -> Universe:
|
||||||
|
data = []
|
||||||
|
y = 0
|
||||||
|
with open(input_file, "r", encoding="utf-8") as input_fd:
|
||||||
|
while line := input_fd.readline():
|
||||||
|
data.append(
|
||||||
|
[Coordinate(x, y, c) for x, c in enumerate(line.rstrip("\n").rstrip())]
|
||||||
|
)
|
||||||
|
y += 1
|
||||||
|
# logging.debug(pformat(data))
|
||||||
|
return Universe(data, part)
|
10
day11/example_input.txt
Normal file
10
day11/example_input.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
...#......
|
||||||
|
.......#..
|
||||||
|
#.........
|
||||||
|
..........
|
||||||
|
......#...
|
||||||
|
.#........
|
||||||
|
.........#
|
||||||
|
..........
|
||||||
|
.......#..
|
||||||
|
#...#.....
|
14
day11/part1.py
Executable file
14
day11/part1.py
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3.11
|
||||||
|
from common import *
|
||||||
|
|
||||||
|
|
||||||
|
def part1(lpairs_of_stars: list[tuple[Coordinate, Coordinate]]) -> int:
|
||||||
|
logging.debug(f"pairs={lpairs_of_stars}")
|
||||||
|
distances = list(map(lambda gs: gs[0].distance(gs[1]), lpairs_of_stars))
|
||||||
|
logging.debug(f"distances={distances}")
|
||||||
|
return sum(distances)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# logging.basicConfig(level=logging.DEBUG)
|
||||||
|
print(part1(pairs_of_stars(parse("input.txt"))))
|
15
day11/part2.py
Executable file
15
day11/part2.py
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3.11
|
||||||
|
|
||||||
|
from common import *
|
||||||
|
|
||||||
|
|
||||||
|
def part2(universe: Universe, factor: int) -> int:
|
||||||
|
pairs = pairs_of_stars(universe)
|
||||||
|
logging.debug(f"pairs={pairs}")
|
||||||
|
distances = list(map(lambda gs: gs[0].distance(gs[1], universe, factor), pairs))
|
||||||
|
logging.debug(f"distances={distances}")
|
||||||
|
return sum(distances)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(part2(parse("input.txt", part=2), 1000000))
|
64
day11/test.py
Executable file
64
day11/test.py
Executable file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3.11
|
||||||
|
import logging
|
||||||
|
from unittest import TestCase, main
|
||||||
|
|
||||||
|
from common import Coordinate, parse, pairs_of_stars
|
||||||
|
|
||||||
|
from part1 import part1
|
||||||
|
from part2 import part2
|
||||||
|
|
||||||
|
|
||||||
|
class Day11Test(TestCase):
|
||||||
|
def test_parse(self):
|
||||||
|
universe_map = [
|
||||||
|
[Coordinate(x, 0, ".") for x in range(4)]
|
||||||
|
+ [Coordinate(4, 0, "#")]
|
||||||
|
+ [Coordinate(x, 0, ".") for x in range(5, 13)],
|
||||||
|
[Coordinate(x, 1, ".") for x in range(9)]
|
||||||
|
+ [Coordinate(9, 1, "#")]
|
||||||
|
+ [Coordinate(x, 1, ".") for x in range(10, 13)],
|
||||||
|
[Coordinate(0, 2, "#")] + [Coordinate(x, 2, ".") for x in range(1, 13)],
|
||||||
|
[Coordinate(x, 3, ".") for x in range(13)],
|
||||||
|
[Coordinate(x, 4, ".") for x in range(13)],
|
||||||
|
[Coordinate(x, 5, ".") for x in range(8)]
|
||||||
|
+ [Coordinate(8, 5, "#")]
|
||||||
|
+ [Coordinate(x, 5, ".") for x in range(9, 13)],
|
||||||
|
[Coordinate(0, 6, "."), Coordinate(1, 6, "#")]
|
||||||
|
+ [Coordinate(x, 6, ".") for x in range(2, 13)],
|
||||||
|
[Coordinate(x, 7, ".") for x in range(12)] + [Coordinate(12, 7, "#")],
|
||||||
|
[Coordinate(x, 8, ".") for x in range(13)],
|
||||||
|
[Coordinate(x, 9, ".") for x in range(13)],
|
||||||
|
[Coordinate(x, 10, ".") for x in range(9)]
|
||||||
|
+ [Coordinate(9, 10, "#")]
|
||||||
|
+ [Coordinate(x, 10, ".") for x in range(10, 13)],
|
||||||
|
[Coordinate(0, 11, "#")]
|
||||||
|
+ [Coordinate(x, 11, ".") for x in range(1, 5)]
|
||||||
|
+ [Coordinate(5, 11, "#")]
|
||||||
|
+ [Coordinate(x, 11, ".") for x in range(6, 13)],
|
||||||
|
]
|
||||||
|
stars = [
|
||||||
|
Coordinate(4, 0, "#"),
|
||||||
|
Coordinate(9, 1, "#"),
|
||||||
|
Coordinate(0, 2, "#"),
|
||||||
|
Coordinate(8, 5, "#"),
|
||||||
|
Coordinate(1, 6, "#"),
|
||||||
|
Coordinate(12, 7, "#"),
|
||||||
|
Coordinate(9, 10, "#"),
|
||||||
|
Coordinate(0, 11, "#"),
|
||||||
|
Coordinate(5, 11, "#"),
|
||||||
|
]
|
||||||
|
universe = parse("example_input.txt")
|
||||||
|
self.assertEqual(universe_map, universe.get_map())
|
||||||
|
self.assertEqual(stars, universe.stars)
|
||||||
|
|
||||||
|
def test_part1(self):
|
||||||
|
self.assertEqual(374, part1(pairs_of_stars(parse("example_input.txt"))))
|
||||||
|
|
||||||
|
def test_part2(self):
|
||||||
|
self.assertEqual(1030, part2(parse("example_input.txt", part=2), factor=10))
|
||||||
|
self.assertEqual(8410, part2(parse("example_input.txt", part=2), factor=100))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
main(verbosity=2)
|
Loading…
x
Reference in New Issue
Block a user