aoc-2023/d05/main.py

133 lines
4.2 KiB
Python
Raw Normal View History

from collections.abc import Generator
from collections.abc import Iterable
from dataclasses import dataclass
from itertools import batched
from itertools import chain
from pathlib import Path
@dataclass
class RangeMapContent:
dest_start: int
source_start: int
length: int
def __contains__(self, key: int) -> int:
return self.source_start <= key < self.source_start + self.length
def __getitem__(self, key: int) -> int:
# print(f"looking for {key} in {self.dest_start} {self.source_start}, {self.length}")
if key not in self:
return key
delta = self.source_start - self.dest_start
return key - delta
class RangeMap:
def __init__(self, *contents: RangeMapContent) -> None:
self.contents = contents
def __getitem__(self, key: int) -> int:
for content in self.contents:
if key in content:
return content[key]
return key
def part1(input: Path, part2=False):
seeds: Iterable[int] = []
maps: dict[str, RangeMap] = {}
map_name: str = ""
map_contents: list[RangeMapContent] = []
with input.open() as f:
for line in f:
line = line.strip()
if not seeds:
seeds = [int(s.strip()) for s in line.partition(":")[2].split()]
if part2:
windows = [
[start, start + length] for start, length in batched(seeds, n=2)
]
windows.sort(key=lambda x: x[0])
merged_windows: list[list[int]] = []
for window in windows:
if not merged_windows:
merged_windows.append(window)
continue
if window[0] > merged_windows[-1][1]:
merged_windows.append(window)
continue
if window[1] > merged_windows[-1][1]:
merged_windows[-1][1] = window[1]
continue
seeds = chain.from_iterable(
range(window[0], window[1]) for window in merged_windows
)
# seeds = set(chain(*[
# range(start, start+length)
# for start, length in batched(seeds, n=2)
# ]))
# print(f"{len(list(seeds))} seeds")
continue
if line == "" and map_name != "":
# print(f"Creating map {map_name} with ranges {map_contents}")
maps[map_name] = RangeMap(*map_contents)
map_name = ""
map_contents.clear()
continue
if map_name == "":
map_name = line.partition(" ")[0]
continue
items = [int(i) for i in line.split()]
dest_start = items[0]
source_start = items[1]
range_length = items[2]
map_contents.append(RangeMapContent(dest_start, source_start, range_length))
maps[map_name] = RangeMap(*map_contents)
# for name, m in maps.items():
# print(f"map {name}")
# for c in m.contents:
# print(f" {c.dest_start} {c.source_start} {c.length}")
min_location: int | None = None
for seed in seeds:
soil = maps["seed-to-soil"][seed]
fertilizer = maps["soil-to-fertilizer"][soil]
water = maps["fertilizer-to-water"][fertilizer]
light = maps["water-to-light"][water]
temperature = maps["light-to-temperature"][light]
humidity = maps["temperature-to-humidity"][temperature]
location = maps["humidity-to-location"][humidity]
if min_location is None or location < min_location:
min_location = location
print(
f"Seed {seed} to soil {soil} to fertilizer {fertilizer} to water {water} to light {light} to temperature {temperature} to humidity {humidity} to location {location}"
)
return min_location
if __name__ == "__main__":
input = Path("./sample.txt")
# input = Path("./input.txt")
answer1 = part1(input)
print("part1", answer1)
answer2 = part1(input, True)
print("part2", answer2)