advent-of-code/2023/day5/src/main.rs

212 lines
5.8 KiB
Rust

use std::collections::{HashMap, VecDeque};
use std::ops::Range;
use DataType::*;
const FILE_STR: &'static str = include_str!("input.txt");
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
enum DataType {
Seed,
Soil,
Fertilizer,
Water,
Light,
Temperature,
Humidity,
Location,
}
impl DataType {
fn try_from(s: &str) -> Option<Self> {
match s {
"seed" => Some(Seed),
"soil" => Some(Soil),
"fertilizer" => Some(Fertilizer),
"water" => Some(Water),
"light" => Some(Light),
"temperature" => Some(Temperature),
"humidity" => Some(Humidity),
"location" => Some(Location),
_ => None,
}
}
}
#[derive(Debug, Default)]
struct DataMap {
from_range: Range<u64>,
to_range: Range<u64>,
}
impl DataMap {
fn new(from_start: u64, to_start: u64, map_length: u64) -> Self {
let to_range = to_start..to_start + map_length;
let from_range = from_start..from_start + map_length;
DataMap {
from_range,
to_range,
}
}
fn get_dest_idx(&self, src_value: u64) -> Option<u64> {
if let Some(idx) = src_value.checked_sub(self.to_range.start) {
if let Some(new_idx) = self.from_range.start.checked_add(idx) {
if new_idx < self.from_range.end {
return Some(new_idx);
}
}
}
None
}
}
type MapMap = HashMap<(DataType, DataType), Vec<DataMap>>;
#[derive(Debug, Default)]
struct Almanac {
seeds: Vec<u64>,
maps: MapMap,
}
impl Almanac {
fn parse(input: &str) -> Self {
let mut data_chunks: VecDeque<_> = input.split("\n\n").collect();
let seeds: Vec<u64> = data_chunks
.pop_front()
.unwrap()
.split(':')
.collect::<Vec<&str>>()
.pop()
.unwrap()
.split_whitespace()
.map(|s| s.trim())
.map(|s| s.parse::<u64>().unwrap())
.collect();
let maps = data_chunks
.into_iter()
.map(|chunk| {
let mut lines: VecDeque<_> = chunk.split("\n").collect();
let type_line_parts: Vec<_> =
lines.pop_front().unwrap().split_whitespace().collect();
let types: Vec<_> = type_line_parts[0]
.split("-to-")
.map(|s| DataType::try_from(s.trim()).unwrap())
.collect();
let (from, to) = (types[0], types[1]);
let mappings: Vec<DataMap> = lines
.into_iter()
.filter(|l| l.len() > 0)
.map(|line| {
line.split_whitespace()
.map(|s| s.trim())
.filter(|s| s.len() > 0)
.map(|s| s.parse::<u64>().unwrap())
.collect::<Vec<u64>>()
})
.map(|m| DataMap::new(m[0], m[1], m[2]))
.collect();
((from, to), mappings)
})
.collect();
Almanac { seeds, maps }
}
fn x_from_y(&self, x: DataType, y: DataType, search: u64) -> u64 {
let maps = self
.maps
.get(&(y, x))
.expect(&format!("Missing mapping from {:?} to {:?}", x, y));
let mut mapped_value: Vec<Option<u64>> = maps
.into_iter()
.map(|dm| dm.get_dest_idx(search))
.filter(|r| r.is_some())
.collect();
if mapped_value.is_empty() {
return search;
}
mapped_value.pop().unwrap().unwrap()
}
fn soil_from_seed(&self, seed: u64) -> u64 {
use DataType::*;
self.x_from_y(Soil, Seed, seed)
}
fn fertilizer_from_soil(&self, soil: u64) -> u64 {
self.x_from_y(Fertilizer, Soil, soil)
}
fn location_from_seed(&self, seed: u64) -> u64 {
let soil = self.soil_from_seed(seed);
let fert = self.fertilizer_from_soil(soil);
let water = self.x_from_y(Water, Fertilizer, fert);
let light = self.x_from_y(Light, Water, water);
let temp = self.x_from_y(Temperature, Light, light);
let humid = self.x_from_y(Humidity, Temperature, temp);
self.x_from_y(Location, Humidity, humid)
}
fn locations_from_seeds(&self) -> HashMap<u64, u64> {
self.seeds
.iter()
.map(|s| (*s, self.location_from_seed(*s)))
.collect()
}
fn lowest_seed_location(&self) -> u64 {
self.locations_from_seeds()
.iter()
.map(|(_, l)| *l)
.min()
.unwrap()
}
}
fn part_one() {
let almanac = Almanac::parse(FILE_STR);
println!("Part 1: Lowest seed location: {}", almanac.lowest_seed_location());
}
fn main() {
part_one();
}
#[cfg(test)]
mod tests {
use crate::Almanac;
use std::collections::HashMap;
const EXAMPLE_FILE_STR: &'static str = include_str!("example-input.txt");
#[test]
fn test_location_from_seed() {
let almanac = Almanac::parse(EXAMPLE_FILE_STR);
assert_eq!(82, almanac.location_from_seed(79));
assert_eq!(43, almanac.location_from_seed(14));
assert_eq!(86, almanac.location_from_seed(55));
assert_eq!(35, almanac.location_from_seed(13));
}
#[test]
fn test_locations_from_seeds() {
let almanac = Almanac::parse(EXAMPLE_FILE_STR);
let expected = HashMap::from([(79, 82), (14, 43), (55, 86), (13, 35)]);
assert_eq!(expected, almanac.locations_from_seeds());
}
#[test]
fn test_lowest_seed_location() {
let almanac = Almanac::parse(EXAMPLE_FILE_STR);
assert_eq!(35, almanac.lowest_seed_location());
}
}