From 0e9fa0fd1f7c09a4946b373ddaed127351102521 Mon Sep 17 00:00:00 2001 From: Christopher Bohn <bohn@unl.edu> Date: Thu, 7 Dec 2023 20:34:45 -0600 Subject: [PATCH] 2023 Day 5 --- 2023/python/Day05.py | 153 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 2023/python/Day05.py diff --git a/2023/python/Day05.py b/2023/python/Day05.py new file mode 100644 index 0000000..9fb0434 --- /dev/null +++ b/2023/python/Day05.py @@ -0,0 +1,153 @@ +from typing import List, Dict, Tuple, Optional, Set + +from ImportData import import_data + +day: int = 5 + +sample_data: List[str] = ''' +seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4 +'''.split('\n')[1:-1] + + +class AlmanacMap: + almanac_maps: Dict[Tuple[str, str], List["AlmanacMap"]] = {} + + def __init__(self, source_category: str, destination_category: str, + source_start: int, destination_start: int, range_length: int): + self.source_category: str = source_category + self.destination_category: str = destination_category + # self.sources: List[int] = [i for i in range(source_start, source_start + range_length)] + # self.destinations: List[int] = [i for i in range(destination_start, destination_start + range_length)] + self.source_start = source_start + self.destination_start = destination_start + self.range_length = range_length + if (source_category, destination_category) not in AlmanacMap.almanac_maps: + AlmanacMap.almanac_maps[(source_category, destination_category)] = [] + AlmanacMap.almanac_maps[(source_category, destination_category)].append(self) + + @staticmethod + def map(source_category: str, destination_category: str, source: int) -> int: + destination: Optional[int] = None + for almanacMap in AlmanacMap.almanac_maps[(source_category, destination_category)]: + # if source in almanacMap.sources: + # destination = almanacMap.destinations[almanacMap.sources.index(source)] + if almanacMap.source_start <= source < almanacMap.source_start + almanacMap.range_length: + destination = almanacMap.destination_start + (source - almanacMap.source_start) + return source if destination is None else destination + + +data_structure: type = Tuple[List[int], Dict[str, List[AlmanacMap]]] + + +def parse_data(data: List[str]) -> data_structure: + mappings: Dict[str, List[AlmanacMap]] = {} + assert data[0].split(':')[0] == 'seeds' + seeds: List[int] = [int(number) for number in data[0].split(':')[1].strip().split()] + map_name: Optional[str] = None + for row in data[2:]: + if not row: + map_name = None + elif row[-4:] == 'map:': + map_name = row[:-5] + mappings[map_name] = [] + else: + mappings[map_name].append(AlmanacMap(map_name.split('-')[0], map_name.split('-')[2], + int(row.split()[1]), int(row.split()[0]), int(row.split()[2]))) + return seeds, mappings + + +def part1(data: data_structure) -> int: + nearest_location: Optional[int] = None + seeds: List[int] = data[0] + for seed in seeds: + soil: int = AlmanacMap.map('seed', 'soil', seed) + fertilizer: int = AlmanacMap.map('soil', 'fertilizer', soil) + water: int = AlmanacMap.map('fertilizer', 'water', fertilizer) + light: int = AlmanacMap.map('water', 'light', water) + temperature: int = AlmanacMap.map('light', 'temperature', light) + humidity: int = AlmanacMap.map('temperature', 'humidity', temperature) + location: int = AlmanacMap.map('humidity', 'location', humidity) + if nearest_location is None or location < nearest_location: + nearest_location = location + return nearest_location + + +def reverse_map(destination: Optional[int], source_name: str, destination_name: str) -> Optional[int]: + if destination is None: + return None + source: Optional[int] = None + almanac_maps = AlmanacMap.almanac_maps[(source_name, destination_name)] + for almanac_map in almanac_maps: + if almanac_map.destination_start <= destination <= almanac_map.destination_start + almanac_map.range_length: + source = almanac_map.source_start + (destination - almanac_map.destination_start) + if source is None: + direct_map = True + for almanac_map in almanac_maps: + if almanac_map.source_start <= destination <= almanac_map.source_start + almanac_map.range_length: + direct_map = False + if direct_map: + source = destination + return source + + +def validate_seed(seed: int, seeds: List[int]) -> Optional[int]: + seed_exists = False + for i in range(0, len(seeds), 2): + if seeds[i] <= seed <= seeds[i] + seeds[i + 1]: + seed_exists = True + return seed if seed_exists else None + + +def part2(data: data_structure) -> int: + location: int = -1 + seed: Optional[int] = None + seeds: List[int] = data[0] + while seed is None: + location += 1 + humidity = reverse_map(location, 'humidity', 'location') + temperature = reverse_map(humidity, 'temperature', 'humidity') + light = reverse_map(temperature, 'light', 'temperature') + water = reverse_map(light, 'water', 'light') + fertilizer = reverse_map(water, 'fertilizer', 'water') + soil = reverse_map(fertilizer, 'soil', 'fertilizer') + seed = validate_seed(reverse_map(soil, 'seed', 'soil'), seeds) + return location + + +if __name__ == '__main__': + production_ready = True + raw_data = import_data(day) if production_ready else sample_data + print(part1(parse_data(raw_data))) + print(part2(parse_data(raw_data))) -- GitLab