2023 day 5.
This commit is contained in:
		
							
								
								
									
										145
									
								
								2023/day5.py
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								2023/day5.py
									
									
									
									
									
								
							| @@ -1,7 +1,5 @@ | ||||
| import sys | ||||
| from collections import defaultdict | ||||
| from dataclasses import dataclass | ||||
| from typing import Literal, TypeAlias | ||||
| from typing import Sequence | ||||
|  | ||||
| MAP_ORDER = [ | ||||
|     "seed", | ||||
| @@ -16,89 +14,116 @@ MAP_ORDER = [ | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
|  | ||||
| seeds: list[int] = [] | ||||
| # mappings from one category to another, each list contains | ||||
| # ranges stored as (source, target, length), ordered by start and | ||||
| # completed to have no "hole" | ||||
| maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {} | ||||
|  | ||||
| # parsing | ||||
| index = 2 | ||||
| while index < len(lines): | ||||
|     l0 = lines[index] | ||||
|     p1, _, p2 = l0.split("-") | ||||
|     p2 = p2.split()[0].strip() | ||||
|     p1, _, p2 = lines[index].split()[0].split("-") | ||||
|  | ||||
|     # extract the existing ranges from the file - we store as (source, target, length) | ||||
|     # whereas the file is in order (target, source, length) | ||||
|     index += 1 | ||||
|     maps[p1, p2] = [] | ||||
|     values: list[tuple[int, int, int]] = [] | ||||
|     while index < len(lines) and lines[index]: | ||||
|         n1, n2, n3 = lines[index].split() | ||||
|         maps[p1, p2].append((int(n1), int(n2), int(n3))) | ||||
|         values.append((int(n2), int(n1), int(n3))) | ||||
|         index += 1 | ||||
|  | ||||
|     # sort by source value | ||||
|     values.sort() | ||||
|  | ||||
|     # add a 'fake' interval starting at 0 if missing | ||||
|     if values[0][0] != 0: | ||||
|         values.insert(0, (0, 0, values[0][0])) | ||||
|  | ||||
|     # fill gaps between intervals | ||||
|     for i in range(len(values) - 1): | ||||
|         next_start = values[i + 1][0] | ||||
|         end = values[i][0] + values[i][2] | ||||
|         if next_start != end: | ||||
|             values.insert( | ||||
|                 i + 1, | ||||
|                 (end, end, next_start - end), | ||||
|             ) | ||||
|  | ||||
|     # add an interval covering values up to at least 2**32 at the end | ||||
|     last_start, _, last_length = values[-1] | ||||
|     values.append((last_start + last_length, last_start + last_length, 2**32)) | ||||
|  | ||||
|     assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:])) | ||||
|     assert values[0][0] == 0 | ||||
|     assert values[-1][0] + values[-1][-1] >= 2**32 | ||||
|  | ||||
|     maps[p1, p2] = values | ||||
|     index += 1 | ||||
|  | ||||
|  | ||||
| def find_location(seed: int) -> int: | ||||
|     value = seed | ||||
|     for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): | ||||
|         for target, start, length in maps[map1, map2]: | ||||
|             if value >= start and value < start + length: | ||||
|                 value = target + (value - start) | ||||
|                 break | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def find_range( | ||||
|     values: tuple[int, int], map: list[tuple[int, int, int]] | ||||
| ) -> list[tuple[int, int]]: | ||||
|     """ | ||||
|     Given an input range, use the given mapping to find the corresponding list of | ||||
|     ranges in the target domain. | ||||
|     """ | ||||
|     r_start, r_length = values | ||||
|     ranges: list[tuple[int, int]] = [] | ||||
|     print(r_start, r_length) | ||||
|     for target, start, length in map: | ||||
|         # start is in the range | ||||
|         if start <= r_start and r_start < start + length: | ||||
|             if r_start + r_length < start + length: | ||||
|                 ranges.append( | ||||
|                     (target + r_start - start, r_length) | ||||
|                 ) | ||||
|             else: | ||||
|                 ranges.append( | ||||
|                     (target + r_start - start, length - (r_start - start)) | ||||
|                 ) | ||||
|         elif start < r_start: | ||||
|             if r_start + r_length < start + length: | ||||
|             ranges.append( | ||||
|                 (target + r_start - start, target + r_start - start + r_length) | ||||
|             ) | ||||
|         elif start >= r_start and r_start >= start | ||||
|  | ||||
|         if r_start <= start and start < start + length: | ||||
|             print(start, length, target) | ||||
|             if r_start + r_length < start + length: | ||||
|                 ranges.append( | ||||
|                     (target + (start - r_start), target + (start - r_start) + length) | ||||
|                 ) | ||||
|             else: | ||||
|                 ranges.append((target + (start - r_start), target + length)) | ||||
|     # find index of the first and last intervals in map that overlaps the input | ||||
|     # interval | ||||
|     index_start, index_end = -1, -1 | ||||
|  | ||||
|     for index_start, (start, _, length) in enumerate(map): | ||||
|         if start <= r_start and start + length > r_start: | ||||
|             break | ||||
|  | ||||
|     for index_end, (start, _, length) in enumerate( | ||||
|         map[index_start:], start=index_start | ||||
|     ): | ||||
|         if r_start + r_length >= start and r_start + r_length < start + length: | ||||
|             break | ||||
|  | ||||
|     assert index_start >= 0 and index_end >= 0 | ||||
|  | ||||
|     # special case if one interval contains everything | ||||
|     if index_start == index_end: | ||||
|         start, target, length = map[index_start] | ||||
|         ranges.append((target + r_start - start, r_length)) | ||||
|     else: | ||||
|         # add the start interval part | ||||
|         start, target, length = map[index_start] | ||||
|         ranges.append((target + r_start - start, start + length - r_start)) | ||||
|  | ||||
|         # add all intervals between the first and last (excluding both) | ||||
|         index = index_start + 1 | ||||
|         while index < index_end: | ||||
|             start, target, length = map[index] | ||||
|             ranges.append((target, length)) | ||||
|             index += 1 | ||||
|  | ||||
|         # add the last interval | ||||
|         start, target, length = map[index_end] | ||||
|         ranges.append((target, r_start + r_length - start)) | ||||
|  | ||||
|     return ranges | ||||
|  | ||||
|  | ||||
| # part 1 | ||||
| seeds = [int(s) for s in lines[0].split(":")[1].strip().split()] | ||||
| answer_1 = min(find_location(seed) for seed in seeds) | ||||
| def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]: | ||||
|     for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): | ||||
|         seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])] | ||||
|     return seeds | ||||
|  | ||||
|  | ||||
| # part 1 - use find_range() with range of length 1 | ||||
| seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()] | ||||
| answer_1 = min(start for start, _ in find_location_ranges(seeds_p1)) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| # part 2 | ||||
| # # part 2 | ||||
| parts = lines[0].split(":")[1].strip().split() | ||||
| seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])] | ||||
|  | ||||
| for seed in range(seeds_p2[0][0], seeds_p2[0][0] + seeds_p2[0][1]): | ||||
|     print(seed, find_location(seed)) | ||||
| print("---") | ||||
|  | ||||
| seeds_p2 = [seeds_p2[0]] | ||||
| for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): | ||||
|     seeds_p2 = [s2 for s1 in seeds_p2 for s2 in find_range(s1, maps[map1, map2])] | ||||
| print(seeds_p2) | ||||
|  | ||||
| answer_2 = ... | ||||
| answer_2 = min(start for start, _ in find_location_ranges(seeds_p2)) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user