123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- 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()
|