Skip to content

Instantly share code, notes, and snippets.

@purplemonkeymad
Created December 5, 2023 20:49
Show Gist options
  • Save purplemonkeymad/ebcc6f043261c58ec90e884a2507f8a5 to your computer and use it in GitHub Desktop.
Save purplemonkeymad/ebcc6f043261c58ec90e884a2507f8a5 to your computer and use it in GitHub Desktop.
day5 part2
import re
import argparse
import sys
def print_error(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def parse_map(section:str):
header,*lines = section.splitlines()
source,destination = (header.split(' ')[0]).split('-to-')
object = Map()
object.source_name = source
object.destination_name = destination
for range in lines:
dest,sour,size = range.strip().split(' ')
range_object = MapRange(int(sour),int(dest),int(size))
object.mapping_list.append(range_object)
return object
class Map:
def __init__(self) -> None:
self.source_name = ""
self.destination_name = ""
self.mapping_list = list()
def __str__(self):
return f"Map: Source:{self.source_name}, Dest:{self.destination_name}, MappingCount:{len(self.mapping_list)}"
def convert_forward(self,number):
for range in self.mapping_list:
if range.in_range(number):
num2 = number - range.source_start + range.destination_start
return num2
return number
def convert_backward(self,number):
for range in self.mapping_list:
if range.in_range(number):
num2 = number + range.source_start - range.destination_start
return num2
return number
class MapRange:
source_start = -1
destination_start = -1
range_size = 0
def __init__(self,source:int ,destination:int ,size:int ):
self.source_start = source
self.destination_start = destination
self.range_size = size
def __repr__(self):
return f"<MapRange source_start:{self.source_start}, destination_start:{self.destination_start}, range:{self.range_size}>"
def __str__(self):
return f"Range: {self.source_start}:{self.source_start+self.range_size} -> {self.destination_start}:{self.destination_start+self.range_size}"
def in_range(self,number:int):
source_end = self.source_start + self.range_size
return (number >= self.source_start and number < source_end)
def combine_map(first:Map, second:Map):
if first.destination_name != second.source_name:
raise f"Cannot combine maps: {first} & {second}"
# we should sort start ranges to make it easy
first_map = list(first.mapping_list)
second_map = list(second.mapping_list)
first_map.sort(key=lambda x:x.destination_start)
second_map.sort(key=lambda x:x.source_start)
new_map = Map()
new_map.source_name = first.source_name
new_map.destination_name = second.destination_name
new_list = list()
while len(first_map) > 0 or len(second_map) > 0:
#end cases
if len(first_map) == 0:
for r in second_map:
new_list.append(r)
second_map.remove(r)
continue
if len(second_map) == 0:
for r in first_map:
new_list.append(r)
first_map.remove(r)
continue
# both are non empty
first_top:MapRange = first_map[0]
second_top:MapRange = second_map[0]
# check for ranges
if (first_top.destination_start < second_top.source_start):
# first lower
if (second_top.source_start > (first_top.destination_start + first_top.range_size ) ):
# out of range, just copy first top
new_list.append(first_top)
first_map.remove(first_top)
continue
# check for overlap
if (second_top.source_start <= (first_top.destination_start + first_top.range_size ) ):
# overlap with first lower
lower_range = second_top.source_start - first_top.destination_start
lower_over = MapRange(first_top.source_start,first_top.destination_start,lower_range)
new_list.append(lower_over)
# update first with new info
first_top.source_start = first_top.source_start + lower_range
first_top.destination_start = first_top.destination_start + lower_range
first_top.range_size = first_top.range_size - lower_range
continue
raise Exception("second neither larger or smaller that size :S, {},{}".format(first_top,second_top))
if (first_top.destination_start == second_top.source_start):
# we only need to check the ranges
if (first_top.range_size == second_top.range_size):
# same, easy copy
new_range = MapRange(first_top.source_start,second_top.destination_start,first_top.range_size)
new_list.append(new_range)
first_map.remove(first_top)
second_map.remove(second_top)
continue
if (first_top.range_size > second_top.range_size):
# second wholy in first
new_range = MapRange(first_top.source_start, second_top.destination_start,second_top.range_size)
new_list.append(new_range)
second_map.remove(second_top)
shift_size = second_top.range_size
first_top.range_size -= shift_size
first_top.destination_start += shift_size
first_top.source_start += shift_size
continue
if (first_top.range_size < second_top.range_size):
# first wholy in second
new_range = MapRange(first_top.source_start, second_top.destination_start,first_top.range_size)
new_list.append(new_range)
first_map.remove(first_top)
shift_size = first_top.range_size
second_top.range_size -= shift_size
second_top.destination_start += shift_size
second_top.source_start += shift_size
continue
raise Exception("unable to determine range sizes: {},{}".format(first_top,second_top))
if (first_top.destination_start > second_top.source_start):
# second lower
if (first_top.destination_start > (second_top.source_start + second_top.range_size ) ):
# out of range, just copy second top
new_list.append(second_top)
second_map.remove(second_top)
continue
# check for overlap
if (first_top.destination_start <= (second_top.source_start + second_top.range_size ) ):
# overlap with second lower
lower_range = first_top.destination_start - second_top.source_start
lower_over = MapRange(second_top.source_start,second_top.destination_start,lower_range)
new_list.append(lower_over)
# update second with new info
second_top.source_start += lower_range
second_top.destination_start += lower_range
second_top.range_size -= lower_range
continue
raise Exception("second neither larger or smaller that size :S, {},{}".format(first_top,second_top))
raise Exception("unable to determine lower positions: {},{}".format(first_top,second_top))
# end while
new_map.mapping_list = new_list
return new_map
def parse_seed(section:str):
header,seed_section = section.split(':')
if (header != 'seeds'):
print_error("seeds header missing, results might be wonky")
# this is now in pairs
seed_id_list = [int(x) for x in re.split(' +',seed_section.strip())]
seed_ranges = zip(seed_id_list[::2],seed_id_list[1::2])
seed_list = list()
for s,e in seed_ranges:
seed_list.append( range(s,s+e) )
return seed_list
class Seed:
seed = -1
soil = -1
fertilizer = -1
water = -1
light = -1
temperature = -1
humidity = -1
location = -1
def calculate_properties(self,maps:dict):
current_type = "seed"
while current_type in maps.keys() and hasattr(self,current_type):
new_n = maps[current_type].convert_forward(getattr(self,current_type))
current_type = maps[current_type].destination_name
setattr(self,current_type,new_n)
def __repr__(self):
return f"<Seed: {self.seed}, soil={self.soil}, fert={self.fertilizer}, water={self.water}, light={self.water}, temp={self.temperature}, hum={self.humidity}, loc={self.location}>"
def __str__(self):
return f"Seed: {self.seed}, soil={self.soil}, fert={self.fertilizer}, water={self.water}, light={self.water}, temp={self.temperature}, hum={self.humidity}, loc={self.location}"
def calculate_properties(maps:dict,number:int):
current_type = "seed"
current_number = number
while current_type in maps.keys():
new_n = maps[current_type].convert_forward(current_number)
current_type = maps[current_type].destination_name
current_number = new_n
return current_number
def main(file_string:str):
seed_part,*map_parts = file_string.split('\n\n')
if (not re.match('seeds:',seed_part)):
raise Exception("File Must start with seeds record.")
seeds = parse_seed(seed_part)
#print(seeds)
maps = [parse_map(x) for x in map_parts]
#[print(str(x)) for x in maps]
flat_map = maps[0]
for i in range(1,len(maps)):
flat_map = combine_map(flat_map,maps[i])
#for j in seeds:
# print("a:{},b:{}".format(j.start,calculate_properties( {"seed": flat_map } ,j.start)))
# print("a:{},b:{}".format(len(j),calculate_properties( {"seed": flat_map } ,len(j))))
flat_map.mapping_list.sort(key=lambda x:x.destination_start)
print(flat_map)
[print(x) for x in flat_map.mapping_list]
#maps_index = { x.source_name:x for x in maps}
#print (maps_index)
# sort output for lowest and work back
for seedstart in flat_map.mapping_list:
local_min = None
seed_map_item:MapRange = seedstart
for seedrange in seeds:
if (seedrange.start >= seed_map_item.source_start and
seedrange.start < (seed_map_item.source_start + seed_map_item.range_size)):
# check values
to_check = seedrange.start
test = calculate_properties({'seed':flat_map},to_check)
if local_min == None or test < local_min:
local_min = test
if (seedrange.stop >= seed_map_item.source_start and
seedrange.stop < (seed_map_item.source_start + seed_map_item.range_size)):
to_check = seed_map_item.source_start
test = calculate_properties({'seed':flat_map},to_check)
if local_min == None or test < local_min:
local_min = test
if local_min != None:
print(local_min)
exit(0)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="day 5 solver")
parser.add_argument("-input",type=str)
parser.add_argument("-part",type=int)
args = parser.parse_args()
filename = args.input
if filename == None:
parser.print_help()
exit(1)
file = open(filename,'r')
main(file.read())
file.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment