133 lines
4.2 KiB
Python
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)
|