day5: problem 1 solution

This commit is contained in:
Keenan Tims
2023-12-06 09:06:59 -08:00
parent 25daf558e6
commit 6d1174e2db
4 changed files with 568 additions and 0 deletions

365
5/src/main.rs Normal file
View File

@ -0,0 +1,365 @@
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::ops::Range;
// --- 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.
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn main() {
println!("Problem 1 solution: {}", problem1(get_input()));
println!("Problem 2 solution: {}", problem2(get_input()));
}
// PARSING
#[derive(Debug)]
struct AlmanacMapping {
source_range: Range<u64>,
dest_range: Range<u64>,
}
impl From<&str> for AlmanacMapping {
fn from(s: &str) -> Self {
let s_nums: Vec<_> = s.split_whitespace().take(3).collect();
let length: u64 = s_nums[2].parse().unwrap();
let source_start: u64 = s_nums[1].parse().unwrap();
let dest_start: u64 = s_nums[0].parse().unwrap();
AlmanacMapping {
source_range: source_start..source_start + length,
dest_range: dest_start..dest_start + length,
}
}
}
impl AlmanacMapping {
fn lookup(&self, key: u64) -> Option<u64> {
if self.source_range.contains(&key) {
match self.source_range.clone().position(|x| x == key) {
Some(i) => self.dest_range.clone().nth(i),
None => None,
}
} else {
None
}
}
}
#[derive(Debug)]
struct AlmanacMappingTable {
name: String,
mappings: Vec<AlmanacMapping>,
}
impl AlmanacMappingTable {
fn lookup(&self, key: u64) -> u64 {
self.mappings
.iter()
.find_map(|map| map.lookup(key))
.unwrap_or(key)
}
}
impl<T: BufRead> From<&mut Lines<T>> for AlmanacMappingTable {
fn from(lines: &mut Lines<T>) -> Self {
let name = lines
.next()
.unwrap()
.unwrap()
.split_once(':')
.unwrap()
.0
.into();
let mut mappings = Vec::new();
for line in lines.map(|l| l.unwrap()) {
if line == "" {
break;
}
mappings.push(AlmanacMapping::from(line.as_str()));
}
AlmanacMappingTable { name, mappings }
}
}
#[derive(Debug)]
struct Almanac {
seeds: Vec<u64>,
seed_to_soil: AlmanacMappingTable,
soil_to_fertilizer: AlmanacMappingTable,
fertilizer_to_water: AlmanacMappingTable,
water_to_light: AlmanacMappingTable,
light_to_temperature: AlmanacMappingTable,
temperature_to_humidity: AlmanacMappingTable,
humidity_to_location: AlmanacMappingTable,
}
impl<T: BufRead> From<Lines<T>> for Almanac {
fn from(mut lines: Lines<T>) -> Self {
let seeds_s = lines.next().unwrap().unwrap();
let seeds: Vec<u64> = seeds_s
.split_once(": ")
.unwrap()
.1
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
lines.next();
Almanac {
seeds,
seed_to_soil: AlmanacMappingTable::from(&mut lines),
soil_to_fertilizer: AlmanacMappingTable::from(&mut lines),
fertilizer_to_water: AlmanacMappingTable::from(&mut lines),
water_to_light: AlmanacMappingTable::from(&mut lines),
light_to_temperature: AlmanacMappingTable::from(&mut lines),
temperature_to_humidity: AlmanacMappingTable::from(&mut lines),
humidity_to_location: AlmanacMappingTable::from(&mut lines),
}
}
}
impl Almanac {
fn lookup(&self, seed_id: u64) -> u64 {
self.humidity_to_location.lookup(
self.temperature_to_humidity.lookup(
self.light_to_temperature.lookup(
self.water_to_light.lookup(
self.fertilizer_to_water.lookup(
self.soil_to_fertilizer
.lookup(self.seed_to_soil.lookup(seed_id)),
),
),
),
),
)
}
}
// PROBLEM 1 solution
// 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?
fn problem1_lowest_location(almanac: &Almanac) -> u64 {
almanac.seeds.iter().map(|seed| almanac.lookup(*seed)).min().unwrap()
}
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let almanac = Almanac::from(input);
problem1_lowest_location(&almanac)
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0
}
#[cfg(test)]
mod tests {
use std::io::{BufRead, Cursor};
use crate::{Almanac, AlmanacMapping, problem1_lowest_location};
const EXAMPLE_ALMANAC: &str = &"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";
#[test]
fn test_almanac_mapping_parse() {
let s = "50 98 2";
let a = AlmanacMapping::from(s);
assert_eq!(a.source_range, 98..100);
assert_eq!(a.dest_range, 50..52);
}
#[test]
fn test_almanac_mapping_lookup() {
let s = "50 98 2";
let a = AlmanacMapping::from(s);
assert_eq!(a.lookup(99), Some(51));
}
#[test]
fn test_almanac_parsing() {
let c = Cursor::new(EXAMPLE_ALMANAC);
let a = Almanac::from(c.lines());
assert_eq!(a.lookup(79), 82);
}
#[test]
fn test_problem1_example() {
let c = Cursor::new(EXAMPLE_ALMANAC);
let a = Almanac::from(c.lines());
assert_eq!(problem1_lowest_location(&a), 35);
}
}