Let's see how far I get this year.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

day05-2.py 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. from helpers import Helper
  2. helper = Helper(debug=True)
  3. load_input = helper.load_input
  4. debug = helper.debug
  5. # FUTURE ME: HERE'S THE KEY
  6. # You don't have to check every seed.
  7. # Instead, create ranges, and use the later steps to cut those ranges
  8. # into chunks. The smallest value in the smallest chunk after the last
  9. # step is the solution. The brute-force solution takes an enormous amount
  10. # of time; the chunk solution is still polynomial time, I think, but
  11. # the n is much smaller (dozens rather than hundreds of millions).
  12. def within(target, start, end):
  13. if start == end:
  14. return target == start
  15. if start > end:
  16. start, end = end, start
  17. return target >= start and end >= target
  18. def chunk_it(old_range, new_range):
  19. o_start, o_end = old_range
  20. n_start, n_end = new_range
  21. debug(o_start, o_end, n_start, n_end)
  22. # The new range is entirely outside the current range
  23. if n_end < o_start or n_start > o_end:
  24. debug("Entirely outside!")
  25. return sorted([old_range, new_range], key=lambda n:n[0])
  26. # The new range is entirely within the current range
  27. if n_start >= o_start and n_end <= o_end:
  28. # We've already accounted for this range
  29. debug("Already got it")
  30. return [old_range]
  31. # The new range starts lower and ends higher
  32. if n_start <= o_start and n_end >= o_end:
  33. # We can replace the old range with the new one
  34. debug("Replacement!")
  35. return [new_range]
  36. # The new range starts lower but ends within
  37. if n_start <= o_start and n_end >= o_start and n_end <= o_end:
  38. debug("Starts before")
  39. return [[n_start, o_end]]
  40. # The new range starts within but ends higher
  41. if n_end >= o_end and n_start >= o_start and n_start <= o_end:
  42. debug("Ends after")
  43. return [[o_start, n_end]]
  44. raise Exception(f"What? What? Nothing matched: {old_range}, {new_range}")
  45. def main():
  46. lines = load_input(5)
  47. maps_dict = {
  48. "seeds": [],
  49. "seed-to-soil": [],
  50. "soil-to-fertilizer": [],
  51. "fertilizer-to-water": [],
  52. "water-to-light": [],
  53. "light-to-temperature": [],
  54. "temperature-to-humidity": [],
  55. "humidity-to-location": [],
  56. }
  57. maps_keys = list(maps_dict.keys())
  58. maps_dict["seeds"] = [int(n) for n in lines[0].split()[1:]]
  59. debug(f"Starting with {len(maps_dict['seeds'])//2} ranges")
  60. real_seeds = []
  61. i, j = 0, 1
  62. while j < len(maps_dict["seeds"]):
  63. new_range = [maps_dict["seeds"][i], maps_dict["seeds"][i] + maps_dict["seeds"][j]]
  64. if len(real_seeds) == 0:
  65. real_seeds.append(new_range)
  66. else:
  67. new_real_seeds = []
  68. for range in real_seeds:
  69. add_ranges = chunk_it(range, new_range)
  70. debug(add_ranges)
  71. for r in add_ranges:
  72. if r not in new_real_seeds:
  73. new_real_seeds.append(r)
  74. real_seeds = list(new_real_seeds)
  75. i += 2
  76. j += 2
  77. debug(real_seeds, i, j)
  78. debug(f"Found {len(real_seeds)} actual ranges")
  79. real_seeds.sort(key=lambda n: n[0])
  80. # debug(real_seeds)
  81. return
  82. # new_start = maps_dict["seeds"][i]
  83. # new_end = new_start + maps_dict["seeds"][j]
  84. # debug(f"Adding seeds in range {new_start}-{new_end}")
  85. # new_seeds = range(new_start, new_end)
  86. # debug(new_seeds)
  87. # real_seeds.append(new_seeds)
  88. # debug(real_seeds)
  89. seeds_list = [{"seed": seed[1]} for seed in real_seeds]
  90. current_key = ""
  91. for line in lines[1:]:
  92. if line == "":
  93. continue
  94. split_line = line.split()
  95. if split_line[0] in maps_keys:
  96. current_key = split_line[0]
  97. else:
  98. maps_dict[current_key].append({
  99. x: int(a)
  100. for (x, a)
  101. in zip(["destination", "source", "length"], split_line)}
  102. )
  103. for seed in seeds_list:
  104. for key in maps_keys[1:]:
  105. source, _, destination = key.split("-")
  106. i = 0
  107. t_list = maps_dict[key]
  108. while i < len(t_list):
  109. s_map = t_list[i]
  110. # debug(f"Checking {seed[source]} against {s_map['source']}, {s_map['source'] + s_map['length']}")
  111. if within(seed[source], s_map["source"], (s_map["source"] + s_map["length"])):
  112. source_distance = seed[source] - s_map["source"]
  113. seed[destination] = s_map["destination"] + source_distance
  114. # debug("{}: {} found in {}+{}, {} is {} + {}".format(
  115. # source,
  116. # seed[source],
  117. # s_map["source"],
  118. # s_map["length"],
  119. # seed[destination],
  120. # s_map["destination"],
  121. # source_distance
  122. # ))
  123. i = len(t_list)
  124. else:
  125. # debug("{}: {} not found in {}+{}, using original".format(
  126. # source,
  127. # seed[source],
  128. # s_map["source"],
  129. # s_map["length"]
  130. # ))
  131. i += 1
  132. if not seed.get(destination, None):
  133. seed[destination] = seed[source]
  134. debug("\n".join([f"{k}: {v}" for k, v in seed.items() for seed in seeds_list]))
  135. print(min(seeds_list, key=lambda x: x["location"]))
  136. if __name__ == "__main__":
  137. main()