codewraith / data /source_files /messy /6b7c7bb0171b.py
slenk's picture
Upload folder using huggingface_hub
eeef81e verified
#!/usr/bin/env python3
# Advent of Code 2021, Day 15 (https://adventofcode.com/2021/day/15)
# Author: Ben Bornstein
import collections
import heapq
Point = collections.namedtuple('Point', ['x', 'y'])
Point.__add__ = lambda self, q: Point(self[0] + q[0], self[1] + q[1])
class RiskMap:
def __init__ (self):
"""Creates a new (empty) risk-level map.
Individual risk-levels as specific positions are accessible via
`RiskMap[Point]`.
See also `RiskMap.load()`
"""
self._factor = 1
self._levels = [ ]
self._nrows = 0
self._ncols = 0
def __getitem__ (self, pos):
"""Returns the risk-level at position `pos`, i.e. `RiskMap[pos]`."""
if self._factor > 1:
risk = self._levels[pos.y % self._nrows][pos.x % self._ncols]
risk += pos.y // self._nrows
risk += pos.x // self._ncols
if risk > 9:
risk = risk % 9
else:
risk = self._levels[pos.y][pos.x]
return risk
@staticmethod
def load (filename):
"""Creates a new risk-level map from `filename`."""
rmap = RiskMap()
with open(filename) as stream:
for line in stream.readlines():
rmap.append([ int(c) for c in line.strip() ])
return rmap
@property
def ncols (self):
"""The number of columns in this `RiskMap`."""
return self._factor * self._ncols
@property
def nrows (self):
"""The number of rows in this `RiskMap`."""
return self._factor * self._nrows
def append (self, row):
"""Appends `row` to this `RiskMap`."""
if len(self._levels) == 0:
self._ncols = len(row)
self._levels.append(row)
self._nrows += 1
def neighbors (self, pos):
"""Iterable 4-neighbors (up, down, left, right) for `pos`ition."""
deltas = (0, -1), (0, 1), (-1, 0), (1, 0)
adjacent = ( pos + Point(*delta) for delta in deltas )
yield from ( p for p in adjacent if self.valid(p) )
def resize (self, factor):
"""Resizes this `RiskMap` by setting its expansion factor to `factor`
copies both horizontally and vertically.
"""
self._factor = factor
def valid (self, pos):
"""Indicates whether or not `pos` is valid (inside this `RiskMap`)."""
return pos.y in range(0, self.nrows) and pos.x in range(0, self.ncols)
def search (rmap, start, end):
"""Searches `RiskMap` `rmap` (breadth-first) to find the least risky
path from `start` to `end`. Returns the total risk of that path.
"""
risk = 0
queue = [ (rmap[p], p) for p in rmap.neighbors(start) ]
visited = { start }
heapq.heapify(queue)
while len(queue) > 0:
risk, current = heapq.heappop(queue)
if current == end:
break
for pos in rmap.neighbors(current):
if pos not in visited:
heapq.heappush( queue, ((rmap[pos] + risk), pos) )
visited.add(pos)
return risk
filename = 'aoc-2021-d15.txt'
rmap = RiskMap.load(filename)
start = Point(0, 0)
end = Point(rmap.ncols - 1, rmap.nrows - 1)
# Part 1
#
# Q: Lowest total risk of any path from the top left to the bottom right?
# A: Total Risk = 755
print(f'Part 1: Total Risk = {search(rmap, start, end):4}')
# Part 2
#
# Q: Lowest total risk of any path from the top left to the bottom right?
# A: Total Risk = 3016
rmap.resize(factor=5)
end = Point(rmap.ncols - 1, rmap.nrows - 1)
print(f'Part 2: Total Risk = {search(rmap, start, end)}')