259 lines
8.7 KiB
Python
259 lines
8.7 KiB
Python
# --- Day 5: If You Give A Seed A Fertilizer ---
|
|
|
|
# You take the boat and find the gardener right where you were told he would be:
|
|
# managing a giant "garden" that looks more to you like a farm.
|
|
|
|
# "A water source? Island Island is the water source!" You point out that Snow
|
|
# Island isn't receiving any water.
|
|
|
|
# "Oh, we had to stop the water because we ran out of sand to filter it with!
|
|
# Can't make snow with dirty water. Don't worry, I'm sure we'll get more sand
|
|
# soon; we only turned off the water a few days... weeks... oh no." His face
|
|
# sinks into a look of horrified realization.
|
|
|
|
# "I've been so busy making sure everyone here has food that I completely forgot
|
|
# to check why we stopped getting more sand! There's a ferry leaving soon that
|
|
# is headed over in that direction - it's much faster than your boat. Could you
|
|
# please go check it out?"
|
|
|
|
# You barely have time to agree to this request when he brings up another.
|
|
# "While you wait for the ferry, maybe you can help us with our food production
|
|
# problem. The latest Island Island Almanac just arrived and we're having
|
|
# trouble making sense of it."
|
|
|
|
# The almanac (your puzzle input) lists all of the seeds that need to be
|
|
# planted. It also lists what type of soil to use with each kind of seed, what
|
|
# type of fertilizer to use with each kind of soil, what type of water to use
|
|
# with each kind of fertilizer, and so on. Every type of seed, soil, fertilizer
|
|
# and so on is identified with a number, but numbers are reused by each category
|
|
# - that is, soil 123 and fertilizer 123 aren't necessarily related to each
|
|
# other.
|
|
|
|
# For example:
|
|
|
|
# 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
|
|
|
|
# The almanac starts by listing which seeds need to be planted: seeds 79, 14,
|
|
# 55, and 13.
|
|
|
|
# The rest of the almanac contains a list of maps which describe how to convert
|
|
# numbers from a source category into numbers in a destination category. That
|
|
# is, the section that starts with seed-to-soil map: describes how to convert a
|
|
# seed number (the source) to a soil number (the destination). This lets the
|
|
# gardener and his team know which soil to use with which seeds, which water to
|
|
# use with which fertilizer, and so on.
|
|
|
|
# Rather than list every source number and its corresponding destination number
|
|
# one by one, the maps describe entire ranges of numbers that can be converted.
|
|
# Each line within a map contains three numbers: the destination range start,
|
|
# the source range start, and the range length.
|
|
|
|
# Consider again the example seed-to-soil map:
|
|
|
|
# 50 98 2
|
|
# 52 50 48
|
|
|
|
# The first line has a destination range start of 50, a source range start of
|
|
# 98, and a range length of 2. This line means that the source range starts at
|
|
# 98 and contains two values: 98 and 99. The destination range is the same
|
|
# length, but it starts at 50, so its two values are 50 and 51. With this
|
|
# information, you know that seed number 98 corresponds to soil number 50 and
|
|
# that seed number 99 corresponds to soil number 51.
|
|
|
|
# The second line means that the source range starts at 50 and contains 48
|
|
# values: 50, 51, ..., 96, 97. This corresponds to a destination range starting
|
|
# at 52 and also containing 48 values: 52, 53, ..., 98, 99. So, seed number 53
|
|
# corresponds to soil number 55.
|
|
|
|
# Any source numbers that aren't mapped correspond to the same destination
|
|
# number. So, seed number 10 corresponds to soil number 10.
|
|
|
|
# So, the entire list of seed numbers and their corresponding soil numbers looks
|
|
# like this:
|
|
|
|
# seed soil
|
|
# 0 0
|
|
# 1 1
|
|
# ... ...
|
|
# 48 48
|
|
# 49 49
|
|
# 50 52
|
|
# 51 53
|
|
# ... ...
|
|
# 96 98
|
|
# 97 99
|
|
# 98 50
|
|
# 99 51
|
|
|
|
# With this map, you can look up the soil number required for each initial seed
|
|
# number:
|
|
|
|
# Seed number 79 corresponds to soil number 81.
|
|
# Seed number 14 corresponds to soil number 14.
|
|
# Seed number 55 corresponds to soil number 57.
|
|
# Seed number 13 corresponds to soil number 13.
|
|
|
|
# The gardener and his team want to get started as soon as possible, so they'd
|
|
# like to know the closest location that needs a seed. Using these maps, find
|
|
# the lowest location number that corresponds to any of the initial seeds. To do
|
|
# this, you'll need to convert each seed number through other categories until
|
|
# you can find its corresponding location number. In this example, the
|
|
# corresponding types are:
|
|
|
|
# Seed 79, soil 81, fertilizer 81, water 81, light 74, temperature 78,
|
|
# humidity 78, location 82.
|
|
# Seed 14, soil 14, fertilizer 53, water 49, light 42, temperature 42,
|
|
# humidity 43, location 43.
|
|
# Seed 55, soil 57, fertilizer 57, water 53, light 46, temperature 82,
|
|
# humidity 82, location 86.
|
|
# Seed 13, soil 13, fertilizer 52, water 41, light 34, temperature 34,
|
|
# humidity 35, location 35.
|
|
|
|
# So, the lowest location number in this example is 35.
|
|
|
|
# What is the lowest location number that corresponds to any of the initial seed
|
|
# numbers?
|
|
|
|
with open("files/P5.txt") as f:
|
|
almanac = [line for line in f.read().strip().split("\n\n")]
|
|
|
|
seed, *maps = almanac
|
|
seeds = [int(s) for s in seed.split(":")[1].split()]
|
|
mappings = [
|
|
[[int(i) for i in line.split()] for line in m.splitlines()[1:]]
|
|
for m in maps
|
|
]
|
|
|
|
|
|
def part1():
|
|
locations = seeds
|
|
for mapping in mappings:
|
|
new_locations = []
|
|
for location in locations:
|
|
new_val = location
|
|
for destination, start, size in mapping:
|
|
if start <= location < start + size:
|
|
new_val = location - start + destination
|
|
break
|
|
new_locations.append(new_val)
|
|
locations = new_locations
|
|
|
|
print(f"The lowest location is {min(locations)}")
|
|
|
|
|
|
# --- Part Two ---
|
|
|
|
# Everyone will starve if you only plant such a small number of seeds.
|
|
# Re-reading the almanac, it looks like the seeds: line actually describes
|
|
# ranges of seed numbers.
|
|
|
|
# The values on the initial seeds: line come in pairs. Within each pair, the
|
|
# first value is the start of the range and the second value is the length of
|
|
# the range. So, in the first line of the example above:
|
|
|
|
# seeds: 79 14 55 13
|
|
|
|
# This line describes two ranges of seed numbers to be planted in the garden.
|
|
# The first range starts with seed number 79 and contains 14 values: 79, 80,
|
|
# ..., 91, 92. The second range starts with seed number 55 and contains 13
|
|
# values: 55, 56, ..., 66, 67.
|
|
|
|
# Now, rather than considering four seed numbers, you need to consider a total
|
|
# of 27 seed numbers.
|
|
|
|
# In the above example, the lowest location number can be obtained from seed
|
|
# number 82, which corresponds to soil 84, fertilizer 84, water 84, light 77,
|
|
# temperature 45, humidity 46, and location 46. So, the lowest location number
|
|
# is 46.
|
|
|
|
# Consider all of the initial seed numbers listed in the ranges on the first
|
|
# line of the almanac. What is the lowest location number that corresponds to
|
|
# any of the initial seed numbers?
|
|
|
|
|
|
def get_ranges(span_start, span_end, map_start, map_end):
|
|
if span_start < map_start and map_end < span_end:
|
|
return [(map_start, map_end)], [
|
|
(span_start, map_start - 1),
|
|
(map_end + 1, span_end),
|
|
]
|
|
elif map_start <= span_start and span_end <= map_end:
|
|
return [(span_start, span_end)], []
|
|
elif span_start < map_start <= span_end:
|
|
return [(map_start, span_end)], [(span_start, map_start - 1)]
|
|
elif span_start <= map_end < span_end:
|
|
return [(span_start, map_end)], [(map_end + 1, span_end)]
|
|
else:
|
|
return [], [(span_start, span_end)]
|
|
|
|
|
|
new_mappings = [
|
|
[
|
|
[destination - start, start, start + size - 1]
|
|
for destination, start, size in m
|
|
]
|
|
for m in mappings
|
|
]
|
|
|
|
|
|
def part2():
|
|
ranges = [
|
|
(seeds[i], seeds[i] + seeds[i + 1] - 1)
|
|
for i in range(0, len(seeds), 2)
|
|
]
|
|
|
|
for mapping in new_mappings:
|
|
new_ranges = []
|
|
for seed_span in ranges:
|
|
unprocessed = [seed_span]
|
|
for offset, map_start, map_end in mapping:
|
|
new_unprocessed = []
|
|
for span_start, span_end in unprocessed:
|
|
map1, map2 = get_ranges(
|
|
span_start, span_end, map_start, map_end
|
|
)
|
|
new_unprocessed += map2
|
|
new_ranges += [(a + offset, b + offset) for a, b in map1]
|
|
unprocessed = new_unprocessed
|
|
new_ranges += unprocessed
|
|
ranges = new_ranges
|
|
|
|
print(f"The lowest location is {min(idx for idx, _ in ranges)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
part1()
|
|
part2()
|