from helpers import Helper helper = Helper(debug=True) load_input = helper.load_input debug = helper.debug # FUTURE ME: HERE'S THE KEY # You don't have to check every seed. # Instead, create ranges, and use the later steps to cut those ranges # into chunks. The smallest value in the smallest chunk after the last # step is the solution. The brute-force solution takes an enormous amount # of time; the chunk solution is still polynomial time, I think, but # the n is much smaller (dozens rather than hundreds of millions). def within(target, start, end): if start == end: return target == start if start > end: start, end = end, start return target >= start and end >= target def chunk_it(old_range, new_range): o_start, o_end = old_range n_start, n_end = new_range debug(o_start, o_end, n_start, n_end) # The new range is entirely outside the current range if n_end < o_start or n_start > o_end: debug("Entirely outside!") return sorted([old_range, new_range], key=lambda n:n[0]) # The new range is entirely within the current range if n_start >= o_start and n_end <= o_end: # We've already accounted for this range debug("Already got it") return [old_range] # The new range starts lower and ends higher if n_start <= o_start and n_end >= o_end: # We can replace the old range with the new one debug("Replacement!") return [new_range] # The new range starts lower but ends within if n_start <= o_start and n_end >= o_start and n_end <= o_end: debug("Starts before") return [[n_start, o_end]] # The new range starts within but ends higher if n_end >= o_end and n_start >= o_start and n_start <= o_end: debug("Ends after") return [[o_start, n_end]] raise Exception(f"What? What? Nothing matched: {old_range}, {new_range}") def main(): lines = load_input(5) maps_dict = { "seeds": [], "seed-to-soil": [], "soil-to-fertilizer": [], "fertilizer-to-water": [], "water-to-light": [], "light-to-temperature": [], "temperature-to-humidity": [], "humidity-to-location": [], } maps_keys = list(maps_dict.keys()) maps_dict["seeds"] = [int(n) for n in lines[0].split()[1:]] debug(f"Starting with {len(maps_dict['seeds'])//2} ranges") real_seeds = [] i, j = 0, 1 while j < len(maps_dict["seeds"]): new_range = [maps_dict["seeds"][i], maps_dict["seeds"][i] + maps_dict["seeds"][j]] if len(real_seeds) == 0: real_seeds.append(new_range) else: new_real_seeds = [] for range in real_seeds: add_ranges = chunk_it(range, new_range) debug(add_ranges) for r in add_ranges: if r not in new_real_seeds: new_real_seeds.append(r) real_seeds = list(new_real_seeds) i += 2 j += 2 debug(real_seeds, i, j) debug(f"Found {len(real_seeds)} actual ranges") real_seeds.sort(key=lambda n: n[0]) # debug(real_seeds) return # new_start = maps_dict["seeds"][i] # new_end = new_start + maps_dict["seeds"][j] # debug(f"Adding seeds in range {new_start}-{new_end}") # new_seeds = range(new_start, new_end) # debug(new_seeds) # real_seeds.append(new_seeds) # debug(real_seeds) seeds_list = [{"seed": seed[1]} for seed in real_seeds] current_key = "" for line in lines[1:]: if line == "": continue split_line = line.split() if split_line[0] in maps_keys: current_key = split_line[0] else: maps_dict[current_key].append({ x: int(a) for (x, a) in zip(["destination", "source", "length"], split_line)} ) for seed in seeds_list: for key in maps_keys[1:]: source, _, destination = key.split("-") i = 0 t_list = maps_dict[key] while i < len(t_list): s_map = t_list[i] # debug(f"Checking {seed[source]} against {s_map['source']}, {s_map['source'] + s_map['length']}") if within(seed[source], s_map["source"], (s_map["source"] + s_map["length"])): source_distance = seed[source] - s_map["source"] seed[destination] = s_map["destination"] + source_distance # debug("{}: {} found in {}+{}, {} is {} + {}".format( # source, # seed[source], # s_map["source"], # s_map["length"], # seed[destination], # s_map["destination"], # source_distance # )) i = len(t_list) else: # debug("{}: {} not found in {}+{}, using original".format( # source, # seed[source], # s_map["source"], # s_map["length"] # )) i += 1 if not seed.get(destination, None): seed[destination] = seed[source] debug("\n".join([f"{k}: {v}" for k, v in seed.items() for seed in seeds_list])) print(min(seeds_list, key=lambda x: x["location"])) if __name__ == "__main__": main()