Advertisement
Guest User

Untitled

a guest
Dec 5th, 2023
186
0
185 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.96 KB | None | 0 0
  1. #
  2. # Part 2 solution is as follows:
  3. #
  4. # First we know how to get from "left" (seed) to "right" (location) for
  5. # any number following the part1 rules.  We rewrite this to ensure that
  6. # it works efficiently even if the ranges are *extremely* large - today's
  7. # gods frown on iterating through the problem space!
  8. #
  9. # Part two, we must look backwards - we look for discontinuities in the
  10. # "location" numberline (where inputs from different humidities come to
  11. # nearly the same location) and follow those discontinuities backwards -
  12. # either because they are the projection of a discontinuity to the right,
  13. # or because they're the right-hand end of a mapping from their "left"
  14. # neighbouring layer.
  15. #
  16. # Eventually we discover get all the seed numbers that are near the start
  17. # of a subsequent discontinuity which follows through to the final layer
  18. # ("location"), i.e. all the seeds which could trace through to either side
  19. # of a discontinuity in the final output which could affect the result.
  20. #
  21. # Search through these (checking both sides of the 'minimum' edge of each
  22. # discontinuity) to get the result without brute forcing a problem space
  23. # that could take all lunchtime or even more!
  24. #
  25. import re
  26. import json
  27. re1 = re.compile("seeds: (.*)")
  28. re2 = re.compile("(\\w+)-to-(\\w+) map:")
  29. re3 = re.compile("([0-9 ]+)")
  30. res = re.compile("\\s+")
  31.  
  32. seed_list = []
  33. map_connections = {} # src name -> dest name
  34. rev_connections = {} # dest name -> src name
  35. map_values = {} # (src_name,dst_name) -> [(src_start, dst_start, length), ...]
  36. map_boundaries = {} # (src_name,dst_name) -> [list of places where a change could occur (lower side only)]
  37.  
  38. #
  39. # Advent of parsing, naturally...
  40. #
  41. src = None
  42. dst = None
  43. for line in open("input.txt"):
  44.     m1 = re1.match(line)
  45.     m2 = re2.match(line)
  46.     m3 = re3.match(line)
  47.     if m1 is not None:
  48.         seed_list = [int(x) for x in res.split(m1.group(1))]
  49.     elif m2 is not None:
  50.         src = m2.group(1)
  51.         dst = m2.group(2)
  52.         map_connections[src] = dst
  53.         rev_connections[dst] = src
  54.         map_values[(src,dst)] = []
  55.     elif m3 is not None:
  56.         (dest_start, source_start, length) = [int(x) for x in res.split(m3.group(1))]
  57.         map_values[(src,dst)].append((source_start, dest_start, length)) # opposite way around
  58.  
  59. #
  60. # Follow a number from left to right [seed to location]:
  61. #
  62. def lookup(src_type, src_num, looking_for_type, depth):
  63.     next_type = map_connections[src_type]
  64.     next_num = src_num # default if no ranges found
  65.     for mapping in map_values[(src_type, next_type)]:
  66.         (source_start, dest_start, length) = mapping
  67.         if source_start <= src_num < source_start + length:
  68.             next_num = dest_start + (src_num - source_start)
  69.     if next_type == looking_for_type:
  70.         return next_num
  71.     else:
  72.         return lookup(next_type, next_num, looking_for_type, depth + 1)
  73.  
  74. #
  75. # This is called recursively by boundary_map_right
  76. #
  77. # Recursively follow boundaries from right to left, given
  78. # that the right hand side has already been added to
  79. # map_boundaries
  80. #
  81. def boundary_map_middle(dst_type, looking_for_type, depth):
  82.     src_type = rev_connections[dst_type]
  83.     map_boundaries[src_type] = []
  84.  
  85.     # 1: ones based on inherant boundaries in this layer
  86.     for mapping in map_values[(src_type, dst_type)]:
  87.         (source_start, dest_start, length) = mapping
  88.         map_boundaries[src_type].append(source_start)
  89.         map_boundaries[src_type].append(source_start - 1)
  90.  
  91.     # 2: ones based on lines from the next-right layer    
  92.     for dst_boundary in map_boundaries[dst_type]:
  93.         src_boundary = dst_boundary
  94.         for mapping in map_values[(src_type, dst_type)]:
  95.             (source_start, dest_start, length) = mapping
  96.             if dest_start <= dst_boundary < dest_start + length:
  97.                 src_boundary = source_start + (dst_boundary - dest_start)
  98.         map_boundaries[src_type].append(src_boundary)
  99.         map_boundaries[src_type].append(src_boundary - 1)
  100.  
  101.     # and recurse to explore the next-left layer
  102.     if src_type != looking_for_type:
  103.         boundary_map_middle(src_type, looking_for_type, depth + 1)
  104.  
  105. # Start the recursive search on the right hand side, by looking
  106. # at boundary conditions where mapped ranges end, and following
  107. # them to the source, including mapping of "straight" connections
  108. # where a non-mapped region might end up having an interesting
  109. # connection.
  110. #
  111. # This calls boundary_map_middle, which becomes recursive after
  112. # this first layer.
  113. #
  114. def boundary_map_right(dst_type):
  115.     src_type = rev_connections[dst_type]
  116.     map_boundaries[src_type] = [0]
  117.     for mapping in map_values[(src_type, dst_type)]:
  118.         (source_start, dest_start, length) = mapping
  119.         map_boundaries[src_type].append(source_start)
  120.         map_boundaries[src_type].append(dest_start -1)
  121.         map_boundaries[src_type].append(dest_start + length + 1)
  122.     boundary_map_middle(src_type, "seed", 0)
  123.  
  124. # Utility to split list into a list of ranges
  125. def array_to_pairs(array):
  126.     result = []
  127.     iterator = iter(array)
  128.     for a in iterator:
  129.         b = next(iterator)
  130.         result.append((a,a+b))
  131.     return result
  132.  
  133. # Utility to check if number is within a list of ranges
  134. def number_in_ranges(ranges, number):
  135.     yes = False
  136.     for r in ranges:
  137.         (a,b) = r
  138.         if a <= number < b:
  139.             yes = True
  140.     return yes
  141.  
  142. # The main program!
  143. part1 = False
  144. if part1:
  145.     # Part 1: search list of seeds
  146.     locations = []
  147.     for seed in seed_list:
  148.         location = lookup("seed", seed, "location", 0)
  149.         locations.append(location)
  150.     print("Part 1 minimum location is %d" % min(locations))
  151.    
  152. else:
  153.     # Start the recursive backwards propogation of interesting
  154.     # boundary points - this takes things which could affect the
  155.     # rightmost layer of the structure and follows them backwards
  156.     # (leftwards) to find boundaries in the previous layer which
  157.     # could cause discontinuities in the mapping of large ranges
  158.     # of numbers from left to right
  159.     #
  160.     boundary_map_right("location")
  161.  
  162.     locations = []
  163.     seed_pairs = array_to_pairs(seed_list)
  164.  
  165.     # Do the search using map_boundaries["seed"], which is the result of looking
  166.     # backwards through the network for points in which conditions change which
  167.     # could lead to a discontinuity through the network:
  168.     for potential_seed in map_boundaries["seed"]:
  169.  
  170.         # Is this seed actually within any of the ranges from the
  171.         # first line of the challenge?
  172.         if number_in_ranges(seed_pairs, potential_seed):
  173.  
  174.             # consider one number as a possible solution
  175.             location = lookup("seed", potential_seed, "location", 0)
  176.             locations.append(location)
  177.  
  178.     # Now we've searched enough very specific seeds at the edge of
  179.     # boundary conditions to be overconfident at having enough
  180.     # coverage to ask it for the final answer...!?
  181.     print("Part 2 (for real) minimum location is %d" % min(locations))
  182.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement