|
| 1 | +# 2023, Day 5: If You Give A Seed A Fertilizer |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +"A water source? Island Island _is_ the water source!" You point out that Snow Island isn't receiving any water. |
| 6 | + |
| 7 | +"Oh, we had to stop the water because we _ran out of sand_ to [filter](https://en.wikipedia.org/wiki/Sand_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. |
| 8 | + |
| 9 | +"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?" |
| 10 | + |
| 11 | +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](https://en.wikipedia.org/wiki/Almanac) just arrived and we're having trouble making sense of it." |
| 12 | + |
| 13 | +## Part 1 |
| 14 | + |
| 15 | +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. |
| 16 | + |
| 17 | +For example: |
| 18 | + |
| 19 | + seeds: 79 14 55 13 |
| 20 | + |
| 21 | + seed-to-soil map: |
| 22 | + 50 98 2 |
| 23 | + 52 50 48 |
| 24 | + |
| 25 | + soil-to-fertilizer map: |
| 26 | + 0 15 37 |
| 27 | + 37 52 2 |
| 28 | + 39 0 15 |
| 29 | + |
| 30 | + fertilizer-to-water map: |
| 31 | + 49 53 8 |
| 32 | + 0 11 42 |
| 33 | + 42 0 7 |
| 34 | + 57 7 4 |
| 35 | + |
| 36 | + water-to-light map: |
| 37 | + 88 18 7 |
| 38 | + 18 25 70 |
| 39 | + |
| 40 | + light-to-temperature map: |
| 41 | + 45 77 23 |
| 42 | + 81 45 19 |
| 43 | + 68 64 13 |
| 44 | + |
| 45 | + temperature-to-humidity map: |
| 46 | + 0 69 1 |
| 47 | + 1 0 69 |
| 48 | + |
| 49 | + humidity-to-location map: |
| 50 | + 60 56 37 |
| 51 | + 56 93 4 |
| 52 | + |
| 53 | + |
| 54 | +The almanac starts by listing which seeds need to be planted: seeds `79`, `14`, `55`, and `13`. |
| 55 | + |
| 56 | +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. |
| 57 | + |
| 58 | +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_. |
| 59 | + |
| 60 | +Consider again the example `seed-to-soil map`: |
| 61 | + |
| 62 | + 50 98 2 |
| 63 | + 52 50 48 |
| 64 | + |
| 65 | + |
| 66 | +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`. |
| 67 | + |
| 68 | +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`. |
| 69 | + |
| 70 | +Any source numbers that _aren't mapped_ correspond to the _same_ destination number. So, seed number `10` corresponds to soil number `10`. |
| 71 | + |
| 72 | +So, the entire list of seed numbers and their corresponding soil numbers looks like this: |
| 73 | + |
| 74 | + seed soil |
| 75 | + 0 0 |
| 76 | + 1 1 |
| 77 | + ... ... |
| 78 | + 48 48 |
| 79 | + 49 49 |
| 80 | + 50 52 |
| 81 | + 51 53 |
| 82 | + ... ... |
| 83 | + 96 98 |
| 84 | + 97 99 |
| 85 | + 98 50 |
| 86 | + 99 51 |
| 87 | + |
| 88 | + |
| 89 | +With this map, you can look up the soil number required for each initial seed number: |
| 90 | + |
| 91 | +* Seed number `79` corresponds to soil number `81`. |
| 92 | +* Seed number `14` corresponds to soil number `14`. |
| 93 | +* Seed number `55` corresponds to soil number `57`. |
| 94 | +* Seed number `13` corresponds to soil number `13`. |
| 95 | + |
| 96 | +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: |
| 97 | + |
| 98 | +* Seed `79`, soil `81`, fertilizer `81`, water `81`, light `74`, temperature `78`, humidity `78`, _location `82`_. |
| 99 | +* Seed `14`, soil `14`, fertilizer `53`, water `49`, light `42`, temperature `42`, humidity `43`, _location `43`_. |
| 100 | +* Seed `55`, soil `57`, fertilizer `57`, water `53`, light `46`, temperature `82`, humidity `82`, _location `86`_. |
| 101 | +* Seed `13`, soil `13`, fertilizer `52`, water `41`, light `34`, temperature `34`, humidity `35`, _location `35`_. |
| 102 | + |
| 103 | +So, the lowest location number in this example is _`35`_. |
| 104 | + |
| 105 | +_What is the lowest location number that corresponds to any of the initial seed numbers?_ |
| 106 | + |
| 107 | +Your puzzle answer was `993500720`. |
| 108 | + |
| 109 | +## Part 2 |
| 110 | + |
| 111 | +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_. |
| 112 | + |
| 113 | +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: |
| 114 | + |
| 115 | + seeds: 79 14 55 13 |
| 116 | + |
| 117 | +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`. |
| 118 | + |
| 119 | +Now, rather than considering four seed numbers, you need to consider a total of _27_ seed numbers. |
| 120 | + |
| 121 | +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`_. |
| 122 | + |
| 123 | +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?_ |
| 124 | + |
| 125 | +Your puzzle answer was `4917124`. |
| 126 | + |
| 127 | +## Solution Notes |
| 128 | + |
| 129 | +Part 1 is almost trivial: Run the numbers through the various maps, determine the minimum result, done. |
| 130 | + |
| 131 | +Part 2 seems to be the same, but in a loop; however, the actual input data uses 9-digit ranges, so a more thoughtful approach is required. The basic idea is to run intervals through the maps, not single numbers. At every mapped range, the intervals are split if they intersect with the beginning or the end of the range, and the inner part is mapped according to the almanac. It's a bit finicky to get the conditions just right, and it requires a fair deal of `if`s and comparisons, but it's manageable. |
| 132 | + |
| 133 | +But wait ... is it even true that a clever approach is a must-have and brute force iteration is a no-go? I estimated that a beefy top-of-the-line computer would need several hours on all cores to crunch through the ~2 billion iterations. Nevertheless, I was intrigued enough to actually implement a solution that's at the same time as dumb as possible, but also as efficient as possible: A Python script that translates the input into a C program that contains ~200 lines of the form `if ((n >= A) && (n < B)) { n = n - A + C; goto end_of_this_map; }` (with `A`, `B` and `C` substituted with the appropriate numbers), and a loop around it to find the minimum value of `n`. When running this, I quickly learned that my initial estimate was off by several orders of magnitude: It completes in about a minute on a single core! An iteration only takes between 100 and 200 CPU clock cycles on average (depending on the compiler, CPU microarchitecture, and whether Spectre/Meltdown mitigations are in effect). Indeed, this is about the most performance-optimal code for a modern CPU: It nicely fits into L1 cache, rarely accesses any other memory, and even though it's quite branchy, those branches are *very* consistent between iterations, making the branch predictor's job easier. |
| 134 | + |
| 135 | +* Part 1, Python: 230 bytes, <100 ms |
| 136 | +* Part 2, Python (interval processing): 423 bytes, <100 ms |
| 137 | +* Part 2, Python-generated C code (brute force): ~70 s |
0 commit comments