use std::{path::Path, str::FromStr}; use crate::{error::AdventError, input::read_into}; #[derive(Debug)] struct RangeMap { start: u32, dest: u32, length: u32, } impl RangeMap { fn map(self: &Self, input: u32) -> Option { let upper_bound: u64 = self.start as u64 + self.length as u64; if input >= self.start && (input as u64) < upper_bound { Some(input - self.start + self.dest) } else { None } } fn inverse_map(&self, input: u32) -> Option { let upper_bound: u64 = self.dest as u64 + self.length as u64; if input >= self.dest && (input as u64) < upper_bound { Some(input - self.dest + self.start) } else { None } } } #[derive(Debug)] struct Mapping { range_maps: Vec } impl Mapping { fn map(self: &Self, input: u32) -> u32 { for range_map in self.range_maps.iter() { match range_map.map(input) { Some(x) => return x, None => continue } } input } fn inverse_map(self: &Self, input: u32) -> u32 { for range_map in self.range_maps.iter() { match range_map.inverse_map(input) { Some(x) => return x, None => continue } } input } } #[derive(Debug)] struct Almanac { seeds: Vec, seed_to_soil_map: Mapping, soil_to_fertilizer_map: Mapping, fertilizer_to_water_map: Mapping, water_to_light_map: Mapping, light_to_temperature_map: Mapping, temperature_to_humidity_map: Mapping, humidity_to_location_map: Mapping } impl Almanac { fn new() -> Self { Almanac { seeds: vec![], seed_to_soil_map: Mapping{range_maps: vec![] }, soil_to_fertilizer_map: Mapping{range_maps: vec![] }, fertilizer_to_water_map: Mapping{range_maps: vec![] }, water_to_light_map: Mapping{range_maps: vec![] }, light_to_temperature_map: Mapping{range_maps: vec![] }, temperature_to_humidity_map: Mapping{range_maps: vec![] }, humidity_to_location_map: Mapping{range_maps: vec![] }, } } fn find_closest(self: &Self, forward: bool) -> Result { let mut total_mapping = vec![ &self.seed_to_soil_map, &self.soil_to_fertilizer_map, &self.fertilizer_to_water_map, &self.water_to_light_map, &self.light_to_temperature_map, &self.temperature_to_humidity_map, &self.humidity_to_location_map, ]; if forward { self.seeds.iter().map(|seed| { total_mapping .iter() .fold(*seed, | input, map| { map.map(input) }) }).min().ok_or(AdventError("mapping procedure went wrong".into())) } else { total_mapping.reverse(); for loc in 0..u32::MAX { let seed = total_mapping .iter() .fold(loc, | input, map| { map.inverse_map(input) }); match self.seeds.binary_search(&seed) { Ok(_) => return Ok(loc), Err(_) => continue } }; Err(AdventError("Scanned location space, found nothing".into())) } } fn interpret_range(self: &mut Self) -> () { let mut vec:Vec = vec![]; let upper_bound = self.seeds.len() / 2; for i in 0..upper_bound { for seed in self.seeds[i*2]..self.seeds[i*2]+self.seeds[i*2+1] { vec.push(seed); } } vec.sort(); self.seeds = vec } } enum State { None, SeedToSoil, SoilToFertilizer, FertilizerToWater, WaterToLight, LightToTemperature, TemperatureToHumidity, HumidityToLocation, } impl FromStr for Almanac { type Err = AdventError; fn from_str(s: &str) -> Result { const SEEDS_HEADER: &'static str = "seeds: "; const SEED_TO_SOIL_MAP_HEADER: &'static str = "seed-to-soil map:"; const SOIL_TO_FERTILIZER_MAP_HEADER: &'static str = "soil-to-fertilizer map:"; const FERTILIZER_TO_WATER_MAP_HEADER: &'static str = "fertilizer-to-water map:"; const WATER_TO_LIGHT_MAP_HEADER: &'static str = "water-to-light map:"; const LIGHT_TO_TEMPERATURE_MAP_HEADER: &'static str = "light-to-temperature map:"; const TEMPERATURE_TO_HUMIDITY_MAP_HEADER: &'static str = "temperature-to-humidity map:"; const HUMIDITY_TO_LOCATION_MAP_HEADER: &'static str = "humidity-to-location map:"; let mut almanac = Almanac::new(); let mut state = State::None; for line in s.lines() { if line.trim().is_empty() { continue; } if line.starts_with(SEEDS_HEADER) { almanac.seeds = line .trim() .split_ascii_whitespace() .skip(1) .map(|s| -> Result { Ok(s.trim().parse::()?) }).collect::, AdventError>>()?; continue; } if !match line.trim() { SEED_TO_SOIL_MAP_HEADER => { state = State::SeedToSoil; true } SOIL_TO_FERTILIZER_MAP_HEADER => { state = State::SoilToFertilizer; true } FERTILIZER_TO_WATER_MAP_HEADER => { state = State::FertilizerToWater; true } WATER_TO_LIGHT_MAP_HEADER => { state = State::WaterToLight; true } LIGHT_TO_TEMPERATURE_MAP_HEADER => { state = State::LightToTemperature; true } TEMPERATURE_TO_HUMIDITY_MAP_HEADER => { state = State::TemperatureToHumidity; true } HUMIDITY_TO_LOCATION_MAP_HEADER => { state = State::HumidityToLocation; true } _ => false } { let mapping = line .trim() .split_ascii_whitespace() .map(|s| -> Result { Ok(s.parse::()?) }).collect::, AdventError>>()?; let range_map = RangeMap { dest: mapping[0], start: mapping[1], length: mapping[2] }; match state { State::None => Err(AdventError("Not in any state".into())), State::SeedToSoil => Ok(almanac.seed_to_soil_map.range_maps.push(range_map)), State::SoilToFertilizer => Ok(almanac.soil_to_fertilizer_map.range_maps.push(range_map)), State::FertilizerToWater => Ok(almanac.fertilizer_to_water_map.range_maps.push(range_map)), State::WaterToLight => Ok(almanac.water_to_light_map.range_maps.push(range_map)), State::LightToTemperature => Ok(almanac.light_to_temperature_map.range_maps.push(range_map)), State::TemperatureToHumidity => Ok(almanac.temperature_to_humidity_map.range_maps.push(range_map)), State::HumidityToLocation => Ok(almanac.humidity_to_location_map.range_maps.push(range_map)), }? } } Ok(almanac) } } pub fn one() -> Result { let almanac = read_into::(Path::new("resources/input5.txt".into()))?; Ok(almanac.find_closest(true)? as u64) } pub fn two() -> Result { let mut almanac = read_into::(Path::new("resources/input5.txt".into()))?; almanac.interpret_range(); Ok(almanac.find_closest(false)? as u64) }