from __future__ import annotations from dataclasses import dataclass from itertools import chain import bisect from typing import Optional import regex as re from aoc import Aoc2, AocSameParser class Map: def __init__(self, group: list[str]) -> None: self.maps = [] for line in group: start_dest, start_source, id_range = [ int(d) for d in re.findall(r"\d+", line) ] bisect.insort(self.maps, (start_source, start_dest - start_source, id_range)) zero_offsets = [] start = 0 for source, _, rnge in self.maps: if source > start: zero_offsets.append((start, 0, source - start)) start = source + rnge for m in zero_offsets: bisect.insort(self.maps, m) def find_bucket(self, x: int) -> Optional[int]: for idx, (source, offset, rnge) in enumerate(self.maps): if x >= source and x < source + rnge: return idx return None def __getitem__(self, x: int) -> int: for (source, offset, rnge) in self.maps: if x >= source and x < source + rnge: return x + offset return x # index = bisect.bisect_left(self.maps, (x+1,0,0)) - 1 # # index -= 1 # # if index < 0: # # return x # # source, offset, id_range = self.maps[index] # if x >= source + id_range: # return x # # if x < source + id_range: # return x + offset # return x # def merge(self, other: Map) -> None: newmappings = [] for osource, ooffset, orange in other.maps: for ssource, soffset, srange in self.maps: if osource >= ssource and osource <= ssource + srange: pass def __repr__(self) -> str: l = [] for k, offset, x in self.maps: l.append(f"{k} - {k + x - 1}: {k+offset} - {k+ offset + x - 1}") return f"Map({', '.join(l)})" @dataclass class Problem: seeds: list[int] maps: dict[tuple[str,str],Map] def convert(self, cat_from: str, cat_to:str, value: int) -> int: target = cat_from output_value = value while target != cat_to: new_target= list(filter(lambda fromto: fromto[0] == target, self.maps.keys()))[0][1] # print(f"{target}({output_value})") output_value = self.maps[(target, new_target)][output_value] # print(f"{new_target}({output_value})") # self.cache[(old_value, target)] = (output_value, new_target) target = new_target return output_value @staticmethod def parse_header(line: str) -> tuple[str, str]: header_match = re.match(r"(?P[^-]*)-to-(?P[^ ]*) map:", line) if header_match is None: raise RuntimeError(f'Could not parse "{line}" as group header') return header_match["source"], header_match["dest"] @staticmethod def from_input(inpt: str) -> Problem: lines = inpt.splitlines() seed_match = re.match(r"seeds:(\s*(?P\d+)\s*)*", lines[0]) if seed_match is None: raise RuntimeError(f'Could not parse "{lines[0]}" as seed list') seeds = [int(s) for s in seed_match.captures("seedid")] groups = [] for line in lines[1:]: if not line: groups.append([]) else: groups[-1].append(line) maps = {Problem.parse_header(group[0]): Map(group[1:]) for group in groups} return Problem(seeds, maps) class Day05(AocSameParser[Problem], Aoc2[Problem, Problem]): def __init__(self) -> None: Aoc2.__init__(self, 2023, 5, example_code_nr2=0) def parseinput(self, inpt: str) -> Problem: return Problem.from_input(inpt) def part1(self, inpt: Problem) -> int: # print([inpt.maps["seed", "soil"][x] for x in range(100)]) # # print(inpt.maps["soil", "fertilizer"]) # print(inpt.maps["soil", "fertilizer"][14]) # print([inpt.maps["soil", "fertilizer"][x] for x in range(100)]) return (min([inpt.convert("seed", "location", x) for x in inpt.seeds])) def part2(self, inpt: Problem) -> int: order = [("seed", "soil"), ("soil", "fertilizer"), ("fertilizer", "water"), ("water", "light"), ("light", "temperature"), ("temperature", "humidity"), ("humidity", "location")] seed_iter = iter(inpt.seeds) seeds = list(zip(seed_iter, seed_iter)) for translation in order: print(translation) print(seeds) new_to_convert = [] current_map = inpt.maps[translation] # print(seeds[1]) # print(current_map) for seed in seeds: cur_range_start, cur_range_range = seed cur_end = cur_range_start + cur_range_range index = current_map.find_bucket(cur_range_start) if index is not None: # print(current_map.maps[index]) section_start, section_offset, section_range = current_map.maps[index] section_end = section_start + section_range if cur_end <= section_end: new_to_convert.append((cur_range_start + section_offset, cur_range_range)) else: # split new_to_convert.append((cur_range_start + section_offset, section_end - cur_range_start)) if index == len(current_map.maps) - 1: new_to_convert.append((section_end, cur_end - section_start)) else: next_start, next_offset, next_range = current_map.maps[index + 1] next_end = next_start + next_range new_to_convert.append((section_end + next_offset, cur_end - section_start)) if cur_end > next_end: print("Oh no") else: new_to_convert.append(seed) seeds = new_to_convert # return min(inpt.convert("seed", "location", x) for x in seeds) def main(): day5 = Day05() print(day5.run_example_2()) if __name__ == "__main__": main()