day23 finished
This commit is contained in:
parent
f743a9636b
commit
db1d74d201
7 changed files with 299 additions and 0 deletions
0
advent/days/day23/__init__.py
Normal file
0
advent/days/day23/__init__.py
Normal file
7
advent/days/day23/data/example01.txt
Normal file
7
advent/days/day23/data/example01.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
....#..
|
||||
..###.#
|
||||
#...#.#
|
||||
.#...##
|
||||
#.###..
|
||||
##.#.##
|
||||
.#..#..
|
||||
6
advent/days/day23/data/example02.txt
Normal file
6
advent/days/day23/data/example02.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.....
|
||||
..##.
|
||||
..#..
|
||||
.....
|
||||
..##.
|
||||
.....
|
||||
12
advent/days/day23/data/expected01_10.txt
Normal file
12
advent/days/day23/data/expected01_10.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
.......#......
|
||||
...........#..
|
||||
..#.#..#......
|
||||
......#.......
|
||||
...#.....#..#.
|
||||
.#......##....
|
||||
.....##.......
|
||||
..#........#..
|
||||
....#.#..#....
|
||||
..............
|
||||
....#..#..#...
|
||||
..............
|
||||
74
advent/days/day23/data/input.txt
Normal file
74
advent/days/day23/data/input.txt
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#..##.....###..##..##...#.#..#..#.#.#.#..#...###..#.....#.##..##.#.##.#.##
|
||||
..###.##.....#...####.........####..##....########.#.#..########.......#..
|
||||
##.....##..##.#..#.#....#.##..###.#..#.#.....##..#.###...##..#..##....#...
|
||||
.####..#...#.....#....###....###.###.##..##..#.##..#.#..##..###....##..###
|
||||
#.##.#.#.#.###..###.#..##...##.##...#.###.#.#.#.....#.##.#.#.#...#...#.###
|
||||
##...#......###...#.#.#.##.#.##..#..##..#.....##.#####..######.##..#.#...#
|
||||
.#....##.....##.#.....#...###..#.#.#..#.......###.....##....##.#..#.##....
|
||||
....#.#.####..#.#...#.##.####....##..#.#..#.#..##.###.####....#.##..###.##
|
||||
...#.####..###..#..##.##..#.#..##.##...#....####.####...#..###.#####.#.#.#
|
||||
#....####.#.#..######...#.#.##.#...######.#..#.#..##...#.......##...#...#.
|
||||
......##..##...#..#....###...#..##.#.#.#...###.#..####.###...####...#..#..
|
||||
...#.###.#.#.#.###...##.......#.###...#....#.....#.....##.##.##.#..###.#.#
|
||||
##.##.###.###..#.#..##.#.####.#.#.#.##...#.###......##...#.#.###..##...#.#
|
||||
.###.###...#...##.#.#.#.#..#....#####..##.#.###..#.#...##.##..#...#..##.#.
|
||||
..###...####.##.#.#...###..##...#.####.#####..#..#..#.#####.#..#.#...#....
|
||||
..###..##.##....##....#.##..#####.#..##..####...#.#..#..##..#..###.##..#..
|
||||
.....##.#....##.##..#.#...##.#.##..#....#.#.#..###.###.###..#..##.#..#.###
|
||||
.#..##..#...#..#..#.##.##..####.#..#..####...##.#...##.###...#.#.##.#..###
|
||||
.#######.#.#.##.#...##.#...#####..###...##..#######.###.#######..##..#####
|
||||
#..##.###.##..#.##.#.#.#.....#.###.##...#.##.##.....#..###.#..#.#..##.####
|
||||
......#...#.###.###.#.##.##.####.####.......#.#.#......##...##.#####.###..
|
||||
#.#.#.#.######...###.#####....#.######.#...#.#.##.#.###.#..##.##.....####.
|
||||
...#.########.##.#....#.##...#...#...#.#.##.##..##...###.#.#######..#...#.
|
||||
..###.###.#.####.###..##...####..#.###.####.##....#.##.#...##..#........#.
|
||||
#...#..###..#.#..#..##....#..###.###..#.######.##.#####..#.#.#.##.###..#.#
|
||||
#.##.#.##.#..#..#######.###.....#...#..##.#......#.##.....#..##.##...#.##.
|
||||
.#.#.####.##.###..#####...##.#..###.##.#.###..####...#####.#..##.##..##.##
|
||||
.#....##..#...#..###...#.##.###.#.#.....#...#.###..##...#.#...#...#..#..#.
|
||||
##.#..######.#####....#..#.#.###...#..#.##..#.#.###.#...##..######..#....#
|
||||
..##.##.#...#......#####...#..##.#..#...#..###.#.###..######.#.....#.#....
|
||||
#.###.###..#.#.#.##.###..#####...###.###.#.#.#..##..#..##..#.#...######.#.
|
||||
####...#....##..####.#.##..##....#.#.###...##..###.#.#..#..##..#.#...####.
|
||||
#...##.##..#..##.##.#..####.#.#####.#....#.###..#...##.##...##....####.#..
|
||||
#.##.###.#..#....#.#.###.##..##..##.###.####.###.#..#..#...#.#.##.##.#....
|
||||
....##..#...#.######....#..#.####...#.....##..#...#.##.....#..#.#...##.##.
|
||||
...##..#...#.#.####.##.#.##...#.###..##.#.....#####...####.##.#.#..#.###.#
|
||||
##..###..#....###.#..##...######..##.####..#..####.###.###.#.###.#.#######
|
||||
..##.#..##.#####..####......#...##.#.#...#....#.##...#.######.###.##.##...
|
||||
#..####.##....##.##...#######.#...#.#..####..###....#......##..#.#######..
|
||||
......#....#..#.##.#...####.#.##.##.##..###.#..####..##..#...##.####..#.##
|
||||
.#####.#....##..#...##..###.#.#.####.##.#####..#..#.###.####.#####.######.
|
||||
...#.....#.#.###.#.#.##....###..#.#.####..##.#.#.##.##.#.###.#...###..#...
|
||||
#......#...####.#....#..#..##.#.#..##.##..#..#..###.##..#...#######..#.##.
|
||||
#.#.#.####.#..#.....##.##.###.#.#...#.#.#.##.#....##..##....##.#..##.#.##.
|
||||
.....#.#..#.#.##.##.#.#.#####.###.##.#..######.#..#.#.#..#####.##...###...
|
||||
#...#..#..#....#...#####.#.##..#.#.##.#.###.##..#####.##...##.#.##.####..#
|
||||
.#.#..#.#...######.#.#..#..#...#####....#..#.#.######.######.#..#.....####
|
||||
##.###....##..####.....#####...###.##..##.#...#.#.#..##.##..#.#.##.#.#.#..
|
||||
###.......###......####.#.##.....######.#.#..##.###.#..#.#..##.##.##..#...
|
||||
..#...#...###.###...#.#...###.####.##.##.#.##.#...##.##.#......##.#.####.#
|
||||
##.#.....#.#.#....#....#.####.#...##.#.#.#..#..###...#####......#...#.#.#.
|
||||
..#.##....#..#.....#....#....#.#.##.......####.#..#.#.##.#.###....##....##
|
||||
#.#..#..#...#####.##.......#.#.##.....##.#.#.###.......#####.#..#.#...#..#
|
||||
..#####.#...##.#.#.#.##......#.#.######....#.####.##...##.###..#.###.#.#..
|
||||
.##..##...#..#.#.#...#.#...#.#####.......#..##..##.##..........#..#..##...
|
||||
.#.#..#####...##.##.##....##..#...###.#####...##..#.....#.#####.#....##..#
|
||||
##.####..#.#...#.#.####.####...#####..#.#.##....#...##.#....##.##.##...##.
|
||||
#..#..#.....#...........#..#...##.####.#..#..##.#.....#...#..#.##.########
|
||||
#.....#..#.#.##.##.#.###..##..#.#.#..#.##.#.#####....####.#..#.###.#..#.#.
|
||||
##...#...##.######.#.#####.......##.#.##..###..####..##..#...##.#####.#.##
|
||||
....#...#..#...####.###.##.#...##...#...####.####..##...##..#.#...##.##.#.
|
||||
#######.###.######..##..##...##...#..#...##.#.#.##..#...#.##.#.#.#.##..#.#
|
||||
...#..#.##.##....###.....#.##.##.####.#######..########.##..##.##.#######.
|
||||
#.#.##.#...#####.##.#...##.#..#.##...#.###.#...##.###...#.##..#...#...###.
|
||||
##.#..#....###.#...#.###...#.##.###.##.####....##...#.##..##......##....#.
|
||||
.###...####.##...##.#...#....#####.##...#.#..##..##.##..#..#..##...#..#...
|
||||
#.##...#.##....##.##...#..#.##..###..##....####.#..#.###.#......##...#..##
|
||||
#...#..#...###......##..#.....###.#.#.##..#.#.###...####.#.##..##..#..##.#
|
||||
..###...#..#..#..####....##.#..##..#....###...###...###.###.#.#.####.####.
|
||||
###......#.......#...#.#.#.....##.##..#...##...#.#...#.#.##.#.#...###.....
|
||||
#.##.##....###.###.###.#..#.####.#.###.#.#.#.#..###..####.##.#.####..#####
|
||||
#.#..#..#.###..#.#...#....#..#.#.#...##.##....#.#.....#.####..#..##..##.##
|
||||
#...#####..#..#.###.##.##.####.##..###....#.###.###.#.#....#.....####.#..#
|
||||
###..##.##.###.#.###..#..###.####..#.#...##.#..###.#...##...#..##.......##
|
||||
160
advent/days/day23/solution.py
Normal file
160
advent/days/day23/solution.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from itertools import count, cycle
|
||||
|
||||
from typing import Iterator
|
||||
|
||||
day_num = 23
|
||||
|
||||
|
||||
def part1(lines: Iterator[str]) -> int:
|
||||
ground = Ground.parse(lines)
|
||||
ground.rounds(10)
|
||||
return ground.count_empty()
|
||||
|
||||
|
||||
def part2(lines: Iterator[str]) -> int:
|
||||
ground = Ground.parse(lines)
|
||||
result = ground.rounds(None)
|
||||
if result is None:
|
||||
assert False, "Unreachable"
|
||||
return result
|
||||
|
||||
|
||||
class Direction(Enum):
|
||||
North = auto()
|
||||
South = auto()
|
||||
West = auto()
|
||||
East = auto()
|
||||
|
||||
def next(self) -> Direction:
|
||||
match self:
|
||||
case Direction.North: return Direction.South
|
||||
case Direction.South: return Direction.West
|
||||
case Direction.West: return Direction.East
|
||||
case Direction.East: return Direction.North
|
||||
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class Position:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def get_adjacent(self, direction: Direction) -> Iterator[Position]:
|
||||
match direction:
|
||||
case Direction.North:
|
||||
yield Position(self.x - 1, self.y - 1)
|
||||
yield Position(self.x, self.y - 1)
|
||||
yield Position(self.x + 1, self.y - 1)
|
||||
case Direction.South:
|
||||
yield Position(self.x - 1, self.y + 1)
|
||||
yield Position(self.x, self.y + 1)
|
||||
yield Position(self.x + 1, self.y + 1)
|
||||
case Direction.West:
|
||||
yield Position(self.x - 1, self.y - 1)
|
||||
yield Position(self.x - 1, self.y)
|
||||
yield Position(self.x - 1, self.y + 1)
|
||||
case Direction.East:
|
||||
yield Position(self.x + 1, self.y - 1)
|
||||
yield Position(self.x + 1, self.y)
|
||||
yield Position(self.x + 1, self.y + 1)
|
||||
|
||||
def get_all_adjacent(self) -> Iterator[Position]:
|
||||
for y in range(-1, 2):
|
||||
for x in range(-1, 2):
|
||||
if x != 0 or y != 0:
|
||||
yield Position(self.x + x, self.y + y)
|
||||
|
||||
def walk(self, direction: Direction) -> Position:
|
||||
match direction:
|
||||
case Direction.North: return Position(self.x, self.y - 1)
|
||||
case Direction.South: return Position(self.x, self.y + 1)
|
||||
case Direction.West: return Position(self.x - 1, self.y)
|
||||
case Direction.East: return Position(self.x + 1, self.y)
|
||||
|
||||
def min(self, other: Position) -> Position:
|
||||
return Position(min(self.x, other.x), min(self.y, other.y))
|
||||
|
||||
def max(self, other: Position) -> Position:
|
||||
return Position(max(self.x, other.x), max(self.y, other.y))
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Ground:
|
||||
map: set[Position]
|
||||
|
||||
def __str__(self) -> str:
|
||||
min_pos, max_pos = self.extent()
|
||||
result = ""
|
||||
for y in range(min_pos.y, max_pos.y + 1):
|
||||
for x in range(min_pos.x, max_pos.x + 1):
|
||||
if Position(x, y) in self.map:
|
||||
result += '#'
|
||||
else:
|
||||
result += '.'
|
||||
result += '\n'
|
||||
return result[:-1]
|
||||
|
||||
def count_empty(self) -> int:
|
||||
min_pos, max_pos = self.extent()
|
||||
return (max_pos.x - min_pos.x + 1) * (max_pos.y - min_pos.y + 1) - len(self.map)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, lines: Iterator[str]) -> Ground:
|
||||
map: set[Position] = set()
|
||||
for y, line in enumerate(lines):
|
||||
for x, tile in enumerate(line):
|
||||
if tile == '#':
|
||||
map.add(Position(x, y))
|
||||
return Ground(map)
|
||||
|
||||
def extent(self) -> tuple[Position, Position]:
|
||||
min_pos = next(iter(self.map))
|
||||
max_pos = min_pos
|
||||
for elf in self.map:
|
||||
min_pos = min_pos.min(elf)
|
||||
max_pos = max_pos.max(elf)
|
||||
return min_pos, max_pos
|
||||
|
||||
def rounds(self, number: int | None) -> int | None:
|
||||
start_dispenser = cycle(iter(Direction))
|
||||
if number is None:
|
||||
it = count(1)
|
||||
else:
|
||||
it = range(1, number + 1)
|
||||
|
||||
for n in it:
|
||||
start = next(start_dispenser)
|
||||
target_map: dict[Position, Position] = {}
|
||||
target_count: dict[Position, int] = {}
|
||||
for elf in self.map:
|
||||
if all(pos not in self.map for pos in elf.get_all_adjacent()):
|
||||
continue
|
||||
|
||||
check_direction = start
|
||||
found = False
|
||||
while not found:
|
||||
found = True
|
||||
for check in elf.get_adjacent(check_direction):
|
||||
if check in self.map:
|
||||
found = False
|
||||
break
|
||||
|
||||
if not found:
|
||||
check_direction = check_direction.next()
|
||||
if check_direction == start:
|
||||
found = True
|
||||
else:
|
||||
target = elf.walk(check_direction)
|
||||
target_map[elf] = target
|
||||
target_count[target] = target_count.get(target, 0) + 1
|
||||
|
||||
changed = False
|
||||
for from_pos, to_pos in target_map.items():
|
||||
if target_count[to_pos] == 1:
|
||||
changed = True
|
||||
self.map.remove(from_pos)
|
||||
self.map.add(to_pos)
|
||||
if not changed:
|
||||
return n
|
||||
40
advent/days/day23/test_solution.py
Normal file
40
advent/days/day23/test_solution.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from advent.common import input
|
||||
|
||||
from .solution import Ground, day_num, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
lines = input.read_lines(day_num, 'example01.txt')
|
||||
expected = 110
|
||||
result = part1(lines)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
lines = input.read_lines(day_num, 'example01.txt')
|
||||
expected = 20
|
||||
result = part2(lines)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_round1():
|
||||
lines = input.read_lines(day_num, 'example02.txt')
|
||||
ground = Ground.parse(lines)
|
||||
expected = "##\n..\n#.\n.#\n#."
|
||||
ground.rounds(1)
|
||||
assert str(ground) == expected
|
||||
|
||||
|
||||
def test_round10():
|
||||
ground = Ground.parse(input.read_lines(day_num, 'example01.txt'))
|
||||
expected = Ground.parse(input.read_lines(day_num, 'expected01_10.txt'))
|
||||
ground.rounds(10)
|
||||
assert str(ground) == str(expected)
|
||||
|
||||
|
||||
def test_round2():
|
||||
lines = input.read_lines(day_num, 'example02.txt')
|
||||
ground = Ground.parse(lines)
|
||||
expected = ".##.\n#...\n...#\n....\n.#.."
|
||||
ground.rounds(2)
|
||||
assert str(ground) == expected
|
||||
Loading…
Add table
Add a link
Reference in a new issue