day23 a bit more coherent

This commit is contained in:
Ruediger Ludwig 2023-01-21 14:44:36 +01:00
parent 3940c75527
commit 840e7abc4d
2 changed files with 60 additions and 53 deletions

View file

@ -93,27 +93,40 @@ class Position:
else: else:
return abs(self.x - other.x) + abs(self.y - other.y) return abs(self.x - other.x) + abs(self.y - other.y)
def component_min(self, *others: Position) -> Position: @classmethod
best = self def component_min(cls, *positions: Position) -> Position:
for next in others: """
best = Position(min(best.x, next.x), min(best.y, next.y)) Returns the position with the minimal value for each component
if best.x <= next.x and best.y <= next.y: Basically this gives the top left corner of the square that
includes all given positions
"""
it = iter(positions)
best = next(it)
for pos in it:
if best.x <= pos.x and best.y <= pos.y:
pass pass
elif best.x >= next.x and best.y >= next.y: elif best.x >= pos.x and best.y >= pos.y:
best = next best = pos
else: else:
best = Position(min(best.x, next.x), min(best.y, next.y)) best = Position(min(best.x, pos.x), min(best.y, pos.y))
return best return best
def component_max(self, *others: Position) -> Position: @classmethod
best = self def component_max(cls, *positions: Position) -> Position:
for next in others: """
if best.x >= next.x and best.y >= next.y: Returns the position with the maximum value for each component
Basically this gives the bottom right corner of the square that
includes all given positions
"""
it = iter(positions)
best = next(it)
for pos in it:
if best.x >= pos.x and best.y >= pos.y:
pass pass
elif best.x <= next.x and best.y <= next.y: elif best.x <= pos.x and best.y <= pos.y:
best = next best = pos
else: else:
best = Position(max(best.x, next.x), max(best.y, next.y)) best = Position(max(best.x, pos.x), max(best.y, pos.y))
return best return best

View file

@ -3,7 +3,7 @@ from dataclasses import dataclass
from enum import IntEnum from enum import IntEnum
from itertools import count, cycle from itertools import count, cycle
from typing import Iterator from typing import Iterable, Iterator
from advent.common.position import Position from advent.common.position import Position
@ -57,7 +57,8 @@ class Ground:
result += '\n' result += '\n'
return result[:-1] return result[:-1]
def count_adjacent(self, elves: dict[Position, int], @classmethod
def check_adjacent(cls, elves: Iterable[Position],
position: Position) -> list[Direction] | None: position: Position) -> list[Direction] | None:
north = False north = False
south = False south = False
@ -118,36 +119,30 @@ class Ground:
return Ground(map) return Ground(map)
def extent(self) -> tuple[Position, Position]: def extent(self) -> tuple[Position, Position]:
it = iter(self.map) return Position.component_min(*self.map), Position.component_max(*self.map)
min_pos = next(it)
max_pos = min_pos
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: def rounds(self, max_rounds: int | None) -> int | None:
start_dispenser = cycle(iter(Direction)) start_dispenser = cycle(iter(Direction))
if number is None: if max_rounds is None:
it = count(1) it = count(1)
else: else:
it = range(1, number + 1) it = range(1, max_rounds + 1)
elves = {position: -1 for position in self.map} elves = {position: 0 for position in self.map}
min_moved, max_moved = self.extent() min_position, max_position = self.extent()
for n in it: for round in it:
min_moved = min_moved + Position(-2, -2) min_position = min_position + Position(-1, -1)
max_moved = max_moved + Position(2, 2) max_position = max_position + Position(1, 1)
start = next(start_dispenser) start = next(start_dispenser)
target_map: dict[Position, Position] = {} proposals: dict[Position, Position] = {}
for from_pos, last_moved in elves.items(): for from_pos, last_moved in elves.items():
if last_moved + 4 <= n: if last_moved + 4 < round:
if not from_pos.is_within(min_moved, max_moved): if not from_pos.is_within(min_position, max_position):
continue continue
adjacent = self.count_adjacent(elves, from_pos) adjacent = self.check_adjacent(elves, from_pos)
if adjacent is None: if adjacent is None:
continue continue
@ -156,30 +151,29 @@ class Ground:
if next_direction in adjacent: if next_direction in adjacent:
next_direction = next_direction.next() next_direction = next_direction.next()
else: else:
target = next_direction.walk(from_pos) to_pos = next_direction.walk(from_pos)
if target not in target_map: if to_pos not in proposals:
target_map[target] = from_pos proposals[to_pos] = from_pos
else: else:
del target_map[target] del proposals[to_pos]
break break
changed = False if not proposals:
self.map = set(elves)
return round
first = True first = True
for to_pos, from_pos in target_map.items(): for to_pos, from_pos in proposals.items():
changed = True
del elves[from_pos] del elves[from_pos]
elves[to_pos] = n elves[to_pos] = round
if first: if first:
max_moved = to_pos max_position = Position.component_max(to_pos, from_pos)
min_moved = to_pos min_position = Position.component_min(to_pos, from_pos)
first = False first = False
else: else:
max_moved = max_moved.component_max(to_pos, from_pos) max_position = Position.component_max(max_position, to_pos, from_pos)
min_moved = min_moved.component_min(to_pos, from_pos) min_position = Position.component_min(min_position, to_pos, from_pos)
if not changed: self.map = set(elves)
self.map = {elf for elf in elves}
return n
self.map = {elf for elf in elves}
return None return None