260 lines
7.3 KiB
Rust
260 lines
7.3 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 !self.to_range.contains(&src_value) {
|
|
return None;
|
|
}
|
|
|
|
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 self.from_range.contains(&new_idx) {
|
|
return Some(new_idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
type MapMap = HashMap<(DataType, DataType), Vec<DataMap>>;
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Almanac {
|
|
seeds: Vec<u64>,
|
|
seed_ranges: Vec<Range<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 even_seeds: Vec<_> = seeds
|
|
.iter()
|
|
.enumerate()
|
|
.filter_map(|(i, s)| if i % 2 == 0 { Some(*s) } else { None })
|
|
.collect();
|
|
let odd_seeds: Vec<_> = seeds
|
|
.iter()
|
|
.enumerate()
|
|
.filter_map(|(i, s)| if i % 2 == 1 { Some(*s) } else { None })
|
|
.collect();
|
|
let seed_ranges = even_seeds
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(i, s)| s..(s + odd_seeds[i]))
|
|
.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,
|
|
seed_ranges,
|
|
maps,
|
|
}
|
|
}
|
|
|
|
fn x_from_y(&self, x: DataType, y: DataType, search: u64) -> u64 {
|
|
self.maps
|
|
.get(&(y, x))
|
|
.expect(&format!("Missing mapping from {:?} to {:?}", x, y))
|
|
.into_iter()
|
|
.filter(|dm| dm.to_range.contains(&search))
|
|
.find_map(|dm| dm.get_dest_idx(search))
|
|
.unwrap_or_else(|| search)
|
|
}
|
|
|
|
fn location_from_seed(&self, seed: u64) -> u64 {
|
|
let soil = self.x_from_y(Soil, Seed, seed);
|
|
let fert = self.x_from_y(Fertilizer, 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 lowest_seed_range_location(&self) -> u64 {
|
|
self.seed_ranges
|
|
.iter()
|
|
.map(|r| {
|
|
r.clone()
|
|
.into_iter()
|
|
.map(|s| self.location_from_seed(s))
|
|
.min()
|
|
.unwrap()
|
|
})
|
|
.min()
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
fn part_one(a: &Almanac) {
|
|
println!("Part 1: Lowest seed location: {}", a.lowest_seed_location());
|
|
}
|
|
|
|
fn part_two(a: &Almanac) {
|
|
/* println!("{:#?}", a.seed_ranges); */
|
|
println!(
|
|
"Part 2: Lowest seed range location: {}",
|
|
a.lowest_seed_range_location()
|
|
);
|
|
}
|
|
|
|
fn main() {
|
|
let almanac = Almanac::parse(FILE_STR);
|
|
part_one(&almanac);
|
|
part_two(&almanac);
|
|
}
|
|
|
|
#[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());
|
|
}
|
|
|
|
#[test]
|
|
fn test_seed_ranges() {
|
|
let almanac = Almanac::parse(EXAMPLE_FILE_STR);
|
|
let range1 = 79u64..93;
|
|
let range2 = 55u64..68;
|
|
|
|
assert!(almanac.seed_ranges.contains(&range1));
|
|
assert!(almanac.seed_ranges.contains(&range2))
|
|
}
|
|
|
|
#[test]
|
|
fn test_lowest_seed_range_location() {
|
|
let almanac = Almanac::parse(EXAMPLE_FILE_STR);
|
|
assert_ne!(45, almanac.lowest_seed_range_location());
|
|
assert_eq!(46, almanac.lowest_seed_range_location());
|
|
assert_ne!(47, almanac.lowest_seed_range_location());
|
|
}
|
|
}
|