import sys from typing import Sequence MAP_ORDER = [ "seed", "soil", "fertilizer", "water", "light", "temperature", "humidity", "location", ] lines = sys.stdin.read().splitlines() # mappings from one category to another, each list contains # ranges stored as (source, target, length), ordered by start and # completed to have no "hole" maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {} # parsing index = 2 while index < len(lines): p1, _, p2 = lines[index].split()[0].split("-") # extract the existing ranges from the file - we store as (source, target, length) # whereas the file is in order (target, source, length) index += 1 values: list[tuple[int, int, int]] = [] while index < len(lines) and lines[index]: n1, n2, n3 = lines[index].split() values.append((int(n2), int(n1), int(n3))) index += 1 # sort by source value values.sort() # add a 'fake' interval starting at 0 if missing if values[0][0] != 0: values.insert(0, (0, 0, values[0][0])) # fill gaps between intervals for i in range(len(values) - 1): next_start = values[i + 1][0] end = values[i][0] + values[i][2] if next_start != end: values.insert( i + 1, (end, end, next_start - end), ) # add an interval covering values up to at least 2**32 at the end last_start, _, last_length = values[-1] values.append((last_start + last_length, last_start + last_length, 2**32)) assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:])) assert values[0][0] == 0 assert values[-1][0] + values[-1][-1] >= 2**32 maps[p1, p2] = values index += 1 def find_range( values: tuple[int, int], map: list[tuple[int, int, int]] ) -> list[tuple[int, int]]: """ Given an input range, use the given mapping to find the corresponding list of ranges in the target domain. """ r_start, r_length = values ranges: list[tuple[int, int]] = [] # find index of the first and last intervals in map that overlaps the input # interval index_start, index_end = -1, -1 for index_start, (start, _, length) in enumerate(map): if start <= r_start and start + length > r_start: break for index_end, (start, _, length) in enumerate( map[index_start:], start=index_start ): if r_start + r_length >= start and r_start + r_length < start + length: break assert index_start >= 0 and index_end >= 0 # special case if one interval contains everything if index_start == index_end: start, target, length = map[index_start] ranges.append((target + r_start - start, r_length)) else: # add the start interval part start, target, length = map[index_start] ranges.append((target + r_start - start, start + length - r_start)) # add all intervals between the first and last (excluding both) index = index_start + 1 while index < index_end: start, target, length = map[index] ranges.append((target, length)) index += 1 # add the last interval start, target, length = map[index_end] ranges.append((target, r_start + r_length - start)) return ranges def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]: for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])] return seeds # part 1 - use find_range() with range of length 1 seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()] answer_1 = min(start for start, _ in find_location_ranges(seeds_p1)) print(f"answer 1 is {answer_1}") # # part 2 parts = lines[0].split(":")[1].strip().split() seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])] answer_2 = min(start for start, _ in find_location_ranges(seeds_p2)) print(f"answer 2 is {answer_2}")