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