-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
403 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
**/__pycache__ | ||
**/input.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import logging | ||
from typing import Generator, List | ||
import re | ||
from data_models import Map, Transformation, Range | ||
|
||
|
||
parse_seeds_rgx = re.compile('[0-9]+') | ||
def read_seeds(lines: Generator[str, None, None]) -> List[int]: | ||
return [int(seed) for seed in parse_seeds_rgx.findall(next(lines).split(':')[1])] | ||
|
||
|
||
def seeds_as_ranges(seeds: List[int]) -> List[Range]: | ||
pairs_gen = (seeds[2*i:2*(i+1)] for i in range(len(seeds) // 2)) | ||
return [Range(start, start + size) for start, size in pairs_gen] | ||
|
||
parse_range_rgx = re.compile('([0-9]+) ([0-9]+) ([0-9]+)') | ||
def read_map(lines: Generator[str, None, None]) -> Map: | ||
next(lines) | ||
|
||
ranges = [] | ||
current_range = next(lines) | ||
while current_range != '': | ||
dst_start, src_start, range_len = (int(n) for n in parse_range_rgx.match(current_range).groups()) # type: ignore | ||
ranges.append(Transformation( | ||
src_range = Range(src_start, src_start + range_len), | ||
dst_range = Range(dst_start, dst_start + range_len), | ||
)) | ||
|
||
try: current_range = next(lines) | ||
except StopIteration: break | ||
|
||
return Map(ranges) | ||
|
||
def read_file(file: str): | ||
with open(file, 'r') as f: | ||
lines = (l.strip() for l in f) | ||
seeds = read_seeds(lines) | ||
|
||
next(lines) | ||
maps = [read_map(lines) for _ in range(7)] | ||
|
||
return seeds, maps | ||
|
||
def p1(args): | ||
seeds, maps = read_file(args.file) | ||
map = Map.merge_maps(maps) | ||
|
||
print(min(map.transform(seed) for seed in seeds)) | ||
|
||
def p2(args): | ||
seeds, maps = read_file(args.file) | ||
map = Map.merge_maps(maps) | ||
|
||
sorted_transformations = sorted(map.transformations, key=lambda t: t.dst_range.start) | ||
for transform in sorted_transformations: | ||
for seed_range in seeds_as_ranges(seeds): | ||
_, union, _ = transform.src_range.merge(seed_range) | ||
if union is not None: | ||
print(union.start + transform.add_src2dst) | ||
return | ||
|
||
if __name__ == '__main__': | ||
import argparse | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('file', type=str, default='input.txt') | ||
parser.add_argument('-v', '--verbose', type=str, choices={'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'}, default='WARNING') | ||
args = parser.parse_args() | ||
|
||
logging.basicConfig(level=args.verbose) | ||
|
||
p1(args) | ||
p2(args) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from typing import List, Tuple, Optional | ||
from attrs import define, field | ||
from functools import reduce | ||
|
||
@define | ||
class Range: | ||
''' Range of numbers: [start, end) ''' | ||
start: int | ||
end : int | ||
|
||
def shift(self, n: int) -> 'Range': return Range( | ||
start = self.start + n, | ||
end = self.end + n | ||
) | ||
|
||
def merge(self, other: 'Range') -> Tuple[Optional['Range'], Optional['Range'], Optional['Range']]: | ||
result: List[Optional[Range]] = [None, None, None] | ||
|
||
# Difference before the start | ||
if other.start < self.start: | ||
result[0] = Range(other.start, min(other.end, self.start)) | ||
|
||
# Intersecction | ||
if not (self.end <= other.start or other.end <= self.start): | ||
result[1] = Range(max(self.start, other.start), min(self.end, other.end)) | ||
|
||
# Difference after the end | ||
if self.end < other.end: | ||
result[2] = Range(max(other.start, self.end), other.end) | ||
|
||
return tuple(result) | ||
|
||
@define | ||
class Transformation: | ||
src_range: Range | ||
dst_range: Range | ||
|
||
add_src2dst: int = field(init=False, repr=False) | ||
add_dst2src: int = field(init=False, repr=False) | ||
def __attrs_post_init__(self): | ||
self.add_src2dst = self.dst_range.start - self.src_range.start | ||
self.add_dst2src = -self.add_src2dst | ||
|
||
def match(self, n: int) -> bool: | ||
return self.src_range.start <= n < self.src_range.end | ||
|
||
@define | ||
class Map: | ||
transformations: List[Transformation] | ||
|
||
@staticmethod | ||
def merge(input: 'Map', output: 'Map') -> 'Map': | ||
merged_transformations = [] | ||
|
||
# Compute merged transformations and input-unmatched transformations | ||
input_transforms: List[Transformation] = [transform for transform in input.transformations] | ||
for out_transform in output.transformations: | ||
unmatched: List[Transformation] = [] | ||
for in_transform in input_transforms: | ||
left, union, right = out_transform.src_range.merge(in_transform.dst_range) | ||
|
||
if left is not None: unmatched.append(Transformation( | ||
src_range = left.shift(in_transform.add_dst2src), | ||
dst_range = left | ||
)) | ||
|
||
if right is not None: unmatched.append(Transformation( | ||
src_range = right.shift(in_transform.add_dst2src), | ||
dst_range = right | ||
)) | ||
|
||
if union is not None: merged_transformations.append(Transformation( | ||
src_range = union.shift(in_transform.add_dst2src), | ||
dst_range = union.shift(out_transform.add_src2dst) | ||
)) | ||
|
||
# Continue with the unmatched ones for the next output | ||
input_transforms = unmatched | ||
|
||
# Compute output-unmatched transformations | ||
output_transforms: List[Transformation] = [transform for transform in output.transformations] | ||
for in_transform in input.transformations: | ||
unmatched: List[Transformation] = [] | ||
for out_transform in output_transforms: | ||
left, _, right = in_transform.dst_range.merge(out_transform.src_range) | ||
|
||
if left is not None: unmatched.append(Transformation( | ||
src_range = left, | ||
dst_range = left.shift(out_transform.add_src2dst) | ||
)) | ||
|
||
if right is not None: unmatched.append(Transformation( | ||
src_range = right, | ||
dst_range = right.shift(out_transform.add_src2dst) | ||
)) | ||
|
||
output_transforms = unmatched | ||
|
||
return Map(merged_transformations + input_transforms + output_transforms) | ||
|
||
@staticmethod | ||
def merge_maps(maps: List['Map']): | ||
return reduce(lambda current, next: Map.merge(current, next), maps[1:], maps[0]) | ||
|
||
def transform(self, n: int) -> int: | ||
for transform in self.transformations: | ||
if transform.match(n): return n + transform.add_src2dst | ||
|
||
return n |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
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 |
Oops, something went wrong.