Day 11 completed

This commit is contained in:
Fedaya 2023-12-11 11:15:05 +01:00
parent 64a270ee39
commit ed55a50ec7
5 changed files with 242 additions and 0 deletions

139
day11/common.py Normal file
View 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
View File

@ -0,0 +1,10 @@
...#......
.......#..
#.........
..........
......#...
.#........
.........#
..........
.......#..
#...#.....

14
day11/part1.py Executable file
View 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
View 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
View 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)