aoc-2023/d05/main.py

133 lines
4.2 KiB
Python

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)