day23 faster

This commit is contained in:
Ruediger Ludwig 2023-01-21 11:21:08 +01:00
parent bb5524601a
commit 3940c75527
4 changed files with 149 additions and 89 deletions

View file

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

View file

@ -1,6 +1,6 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum, auto
from enum import IntEnum
from itertools import count, cycle
from typing import Iterator
@ -24,96 +24,106 @@ def part2(lines: Iterator[str]) -> int:
return result
class Direction(Enum):
North = auto()
South = auto()
West = auto()
East = auto()
class Direction(IntEnum):
North = 0
South = 1
West = 2
East = 3
def next(self) -> Direction:
return Direction((self + 1) % 4)
def walk(self, position: Position) -> Position:
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 ElfPosition(Position):
def get_adjacent(self, direction: Direction) -> Iterator[ElfPosition]:
match direction:
case Direction.North:
yield ElfPosition(self.x - 1, self.y - 1)
yield ElfPosition(self.x, self.y - 1)
yield ElfPosition(self.x + 1, self.y - 1)
case Direction.South:
yield ElfPosition(self.x - 1, self.y + 1)
yield ElfPosition(self.x, self.y + 1)
yield ElfPosition(self.x + 1, self.y + 1)
case Direction.West:
yield ElfPosition(self.x - 1, self.y - 1)
yield ElfPosition(self.x - 1, self.y)
yield ElfPosition(self.x - 1, self.y + 1)
case Direction.East:
yield ElfPosition(self.x + 1, self.y - 1)
yield ElfPosition(self.x + 1, self.y)
yield ElfPosition(self.x + 1, self.y + 1)
def get_all_adjacent(self) -> Iterator[ElfPosition]:
for y in range(-1, 2):
for x in range(-1, 2):
if x != 0 or y != 0:
yield ElfPosition(self.x + x, self.y + y)
def walk(self, direction: Direction) -> ElfPosition:
match direction:
case Direction.North: return ElfPosition(self.x, self.y - 1)
case Direction.South: return ElfPosition(self.x, self.y + 1)
case Direction.West: return ElfPosition(self.x - 1, self.y)
case Direction.East: return ElfPosition(self.x + 1, self.y)
def min(self, other: ElfPosition) -> ElfPosition:
return ElfPosition(min(self.x, other.x), min(self.y, other.y))
def max(self, other: ElfPosition) -> ElfPosition:
return ElfPosition(max(self.x, other.x), max(self.y, other.y))
case Direction.North: return Position(position.x, position.y - 1)
case Direction.South: return Position(position.x, position.y + 1)
case Direction.West: return Position(position.x - 1, position.y)
case Direction.East: return Position(position.x + 1, position.y)
@dataclass(slots=True)
class Ground:
map: set[ElfPosition]
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 ElfPosition(x, y) in self.map:
if Position(x, y) in self.map:
result += '#'
else:
result += '.'
result += '\n'
return result[:-1]
def count_adjacent(self, elves: dict[Position, int],
position: Position) -> list[Direction] | None:
north = False
south = False
west = False
east = False
if (position + Position(-1, -1)) in elves:
north = True
west = True
if (position + Position(1, 1)) in elves:
south = True
east = True
if north is False:
north = (position + Position(0, -1)) in elves
if south is False:
south = (position + Position(0, 1)) in elves
if west is False:
west = (position + Position(-1, 0)) in elves
if east is False:
east = (position + Position(1, 0)) in elves
if north is False or east is False:
if (position + Position(1, -1)) in elves:
north = True
east = True
if south is False or west is False:
if (position + Position(-1, 1)) in elves:
south = True
west = True
if north == south == east == west:
return None
adjacent: list[Direction] = []
if north:
adjacent.append(Direction.North)
if south:
adjacent.append(Direction.South)
if west:
adjacent.append(Direction.West)
if east:
adjacent.append(Direction.East)
return adjacent
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[ElfPosition] = set()
map: set[Position] = set()
for y, line in enumerate(lines):
for x, tile in enumerate(line):
if tile == '#':
map.add(ElfPosition(x, y))
map.add(Position(x, y))
return Ground(map)
def extent(self) -> tuple[ElfPosition, ElfPosition]:
min_pos = next(iter(self.map))
def extent(self) -> tuple[Position, Position]:
it = iter(self.map)
min_pos = next(it)
max_pos = min_pos
for elf in self.map:
min_pos = min_pos.min(elf)
max_pos = max_pos.max(elf)
for elf in it:
min_pos = min_pos.component_min(elf)
max_pos = max_pos.component_max(elf)
return min_pos, max_pos
def rounds(self, number: int | None) -> int | None:
@ -123,37 +133,53 @@ class Ground:
else:
it = range(1, number + 1)
elves = {position: -1 for position in self.map}
min_moved, max_moved = self.extent()
for n in it:
min_moved = min_moved + Position(-2, -2)
max_moved = max_moved + Position(2, 2)
start = next(start_dispenser)
target_map: dict[ElfPosition, ElfPosition] = {}
target_count: dict[ElfPosition, int] = {}
for elf in self.map:
if all(pos not in self.map for pos in elf.get_all_adjacent()):
target_map: dict[Position, Position] = {}
for from_pos, last_moved in elves.items():
if last_moved + 4 <= n:
if not from_pos.is_within(min_moved, max_moved):
continue
adjacent = self.count_adjacent(elves, from_pos)
if adjacent is None:
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
next_direction = start
while True:
if next_direction in adjacent:
next_direction = next_direction.next()
else:
target = elf.walk(check_direction)
target_map[elf] = target
target_count[target] = target_count.get(target, 0) + 1
target = next_direction.walk(from_pos)
if target not in target_map:
target_map[target] = from_pos
else:
del target_map[target]
break
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)
first = True
for to_pos, from_pos in target_map.items():
changed = True
del elves[from_pos]
elves[to_pos] = n
if first:
max_moved = to_pos
min_moved = to_pos
first = False
else:
max_moved = max_moved.component_max(to_pos, from_pos)
min_moved = min_moved.component_min(to_pos, from_pos)
if not changed:
self.map = {elf for elf in elves}
return n
self.map = {elf for elf in elves}
return None