2023 day 5.
This commit is contained in:
parent
508c8cdc42
commit
0f8a272b71
143
2023/day5.py
143
2023/day5.py
@ -1,7 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from typing import Sequence
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Literal, TypeAlias
|
|
||||||
|
|
||||||
MAP_ORDER = [
|
MAP_ORDER = [
|
||||||
"seed",
|
"seed",
|
||||||
@ -16,89 +14,116 @@ MAP_ORDER = [
|
|||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
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]]] = {}
|
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
|
||||||
|
|
||||||
# parsing
|
# parsing
|
||||||
index = 2
|
index = 2
|
||||||
while index < len(lines):
|
while index < len(lines):
|
||||||
l0 = lines[index]
|
p1, _, p2 = lines[index].split()[0].split("-")
|
||||||
p1, _, p2 = l0.split("-")
|
|
||||||
p2 = p2.split()[0].strip()
|
|
||||||
|
|
||||||
|
# extract the existing ranges from the file - we store as (source, target, length)
|
||||||
|
# whereas the file is in order (target, source, length)
|
||||||
index += 1
|
index += 1
|
||||||
maps[p1, p2] = []
|
values: list[tuple[int, int, int]] = []
|
||||||
while index < len(lines) and lines[index]:
|
while index < len(lines) and lines[index]:
|
||||||
n1, n2, n3 = lines[index].split()
|
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
|
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
|
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(
|
def find_range(
|
||||||
values: tuple[int, int], map: list[tuple[int, int, int]]
|
values: tuple[int, int], map: list[tuple[int, int, int]]
|
||||||
) -> list[tuple[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
|
r_start, r_length = values
|
||||||
ranges: list[tuple[int, int]] = []
|
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:
|
# find index of the first and last intervals in map that overlaps the input
|
||||||
print(start, length, target)
|
# interval
|
||||||
if r_start + r_length < start + length:
|
index_start, index_end = -1, -1
|
||||||
ranges.append(
|
|
||||||
(target + (start - r_start), target + (start - r_start) + length)
|
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:
|
else:
|
||||||
ranges.append((target + (start - r_start), target + length))
|
# 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
|
return ranges
|
||||||
|
|
||||||
|
|
||||||
# part 1
|
def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]:
|
||||||
seeds = [int(s) for s in lines[0].split(":")[1].strip().split()]
|
for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]):
|
||||||
answer_1 = min(find_location(seed) for seed in seeds)
|
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}")
|
print(f"answer 1 is {answer_1}")
|
||||||
|
|
||||||
# part 2
|
# # part 2
|
||||||
parts = lines[0].split(":")[1].strip().split()
|
parts = lines[0].split(":")[1].strip().split()
|
||||||
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
|
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
|
||||||
|
answer_2 = min(start for start, _ in find_location_ranges(seeds_p2))
|
||||||
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 = ...
|
|
||||||
print(f"answer 2 is {answer_2}")
|
print(f"answer 2 is {answer_2}")
|
||||||
|
Loading…
Reference in New Issue
Block a user