271 lines
6.8 KiB
Rust
271 lines
6.8 KiB
Rust
|
use std::{collections::HashMap, ops::Range};
|
||
|
|
||
|
use regex::Regex;
|
||
|
|
||
|
pub fn run() {
|
||
|
let input = include_str!("../input/day5.txt");
|
||
|
// let input = r#"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"#;
|
||
|
|
||
|
let almanac = Almanac::parse_seed_list(input);
|
||
|
let part1 = almanac.convert_seeds_to_locations();
|
||
|
dbg!(part1.iter().min().unwrap());
|
||
|
|
||
|
let almanac2 = Almanac::parse_seed_range(input);
|
||
|
let part2 = almanac2.convert_seeds_to_locations();
|
||
|
dbg!(part2.iter().min().unwrap());
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Clone)]
|
||
|
struct Almanac<S: IntoIterator<Item = i64> + Clone> {
|
||
|
seeds: S,
|
||
|
maps: HashMap<(String, String), Map>,
|
||
|
}
|
||
|
|
||
|
fn parse_maps<'a>(parts: impl Iterator<Item = &'a str>) -> HashMap<(String, String), Map> {
|
||
|
let mut maps = HashMap::new();
|
||
|
let map_name_re = Regex::new(r#"(\w+)-to-(\w+) map:"#).unwrap();
|
||
|
for map_s in parts {
|
||
|
let (name, rest) = map_s.split_once("\n").unwrap();
|
||
|
let map_captures = map_name_re.captures(name).unwrap();
|
||
|
let map = Map::parse(rest);
|
||
|
maps.insert(
|
||
|
(map_captures[1].to_owned(), map_captures[2].to_owned()),
|
||
|
map,
|
||
|
);
|
||
|
}
|
||
|
maps
|
||
|
}
|
||
|
|
||
|
impl Almanac<Vec<i64>> {
|
||
|
fn parse_seed_list(s: &str) -> Self {
|
||
|
let mut parts = s.split("\n\n");
|
||
|
let seeds_s = parts.next().unwrap();
|
||
|
assert!(seeds_s.starts_with("seeds: "));
|
||
|
let seeds_s = seeds_s.split_at("seeds: ".len()).1;
|
||
|
let seeds = seeds_s
|
||
|
.split(" ")
|
||
|
.map(|s| s.parse::<i64>().unwrap())
|
||
|
.collect::<Vec<_>>();
|
||
|
Self {
|
||
|
seeds,
|
||
|
maps: parse_maps(parts),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Clone, PartialEq)]
|
||
|
struct Seeds {
|
||
|
ranges: Vec<Range<i64>>,
|
||
|
cur: Option<Range<i64>>,
|
||
|
}
|
||
|
|
||
|
impl Iterator for Seeds {
|
||
|
type Item = i64;
|
||
|
|
||
|
fn next(&mut self) -> Option<Self::Item> {
|
||
|
if self.cur.is_none() || self.cur.as_ref().unwrap().is_empty() {
|
||
|
if self.ranges.is_empty() {
|
||
|
self.cur = None;
|
||
|
return None;
|
||
|
} else {
|
||
|
self.cur = Some(self.ranges.remove(0));
|
||
|
return self.next();
|
||
|
}
|
||
|
} else {
|
||
|
let cur = self.cur.as_ref().unwrap();
|
||
|
let ret = cur.start;
|
||
|
self.cur = Some((cur.start + 1)..cur.end);
|
||
|
return Some(ret);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Almanac<Seeds> {
|
||
|
fn parse_seed_range(s: &str) -> Self {
|
||
|
let mut parts = s.split("\n\n");
|
||
|
let seeds_s = parts.next().unwrap();
|
||
|
assert!(seeds_s.starts_with("seeds: "));
|
||
|
let seeds_s = seeds_s.split_at("seeds: ".len()).1;
|
||
|
let numbers = seeds_s
|
||
|
.split(" ")
|
||
|
.map(|s| s.parse::<i64>().unwrap())
|
||
|
.collect::<Vec<_>>();
|
||
|
assert!(numbers.len() % 2 == 0);
|
||
|
let ranges = numbers
|
||
|
.chunks(2)
|
||
|
.map(|chunk| chunk[0]..(chunk[0] + chunk[1]))
|
||
|
.collect::<Vec<_>>();
|
||
|
Self {
|
||
|
seeds: Seeds { ranges, cur: None },
|
||
|
maps: parse_maps(parts),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<S: IntoIterator<Item = i64> + Clone> Almanac<S> {
|
||
|
fn convert(&self, xs: &mut Vec<i64>, from: &str, to: &str) {
|
||
|
let map = self
|
||
|
.maps
|
||
|
.get(&(from.to_owned(), to.to_owned()))
|
||
|
.expect(&format!("conversion from {} to {}", from, to));
|
||
|
for i in 0..xs.len() {
|
||
|
xs[i] = map.convert(xs[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn convert_seeds_to_locations(&self) -> Vec<i64> {
|
||
|
let path = &[
|
||
|
"soil",
|
||
|
"fertilizer",
|
||
|
"water",
|
||
|
"light",
|
||
|
"temperature",
|
||
|
"humidity",
|
||
|
"location",
|
||
|
];
|
||
|
let mut cur_type = "seed";
|
||
|
let mut cur = self.seeds.clone().into_iter().collect::<Vec<_>>();
|
||
|
for next in path {
|
||
|
self.convert(&mut cur, cur_type, next);
|
||
|
cur_type = next;
|
||
|
}
|
||
|
cur
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Clone, PartialEq)]
|
||
|
struct Map {
|
||
|
// range of source and offset to dest
|
||
|
ranges: Vec<(Range<i64>, i64)>,
|
||
|
}
|
||
|
|
||
|
impl Map {
|
||
|
fn parse(s: &str) -> Self {
|
||
|
let ranges = s
|
||
|
.lines()
|
||
|
.map(|l| {
|
||
|
let mut parts = l.trim().split(" ");
|
||
|
let dest_start: i64 = parts.next().unwrap().parse().unwrap();
|
||
|
let source_start: i64 = parts.next().unwrap().parse().unwrap();
|
||
|
let len: i64 = parts.next().unwrap().parse().unwrap();
|
||
|
assert_eq!(parts.next(), None);
|
||
|
(
|
||
|
source_start..(source_start + len),
|
||
|
dest_start - source_start,
|
||
|
)
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
Self { ranges }
|
||
|
}
|
||
|
|
||
|
fn convert(&self, source: i64) -> i64 {
|
||
|
for (range, offset) in self.ranges.iter() {
|
||
|
if range.contains(&source) {
|
||
|
return source + offset;
|
||
|
}
|
||
|
}
|
||
|
source
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
|
||
|
#[test]
|
||
|
fn test_parse_map() {
|
||
|
assert_eq!(
|
||
|
Map::parse("50 98 2\n52 50 48"),
|
||
|
Map {
|
||
|
ranges: vec![(98..100, -48), (50..98, 2),],
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_map_convert() {
|
||
|
let m = Map {
|
||
|
ranges: vec![(98..100, -48), (50..98, 2)],
|
||
|
};
|
||
|
assert_eq!(m.convert(98), 50);
|
||
|
assert_eq!(m.convert(53), 55);
|
||
|
assert_eq!(m.convert(10), 10);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_parse_almanac() {
|
||
|
let a = Almanac::parse_seed_list(
|
||
|
r#"seeds: 79 14 55 13
|
||
|
|
||
|
seed-to-soil map:
|
||
|
50 98 2
|
||
|
52 50 48"#,
|
||
|
);
|
||
|
assert_eq!(a.seeds, vec![79, 14, 55, 13]);
|
||
|
let k = ("seed".to_owned(), "soil".to_owned());
|
||
|
assert_eq!(a.maps.keys().collect::<Vec<_>>(), vec![&k]);
|
||
|
assert_eq!(
|
||
|
a.maps[&k],
|
||
|
Map {
|
||
|
ranges: vec![(98..100, -48), (50..98, 2)],
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_parse_almanac_seed_ranges() {
|
||
|
let a = Almanac::parse_seed_range("seeds: 1 2 3 4");
|
||
|
assert_eq!(
|
||
|
a.seeds,
|
||
|
Seeds {
|
||
|
ranges: vec![1..3, 3..7],
|
||
|
cur: None,
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_seeds_iter() {
|
||
|
assert_eq!(
|
||
|
Seeds {
|
||
|
ranges: vec![1..3, 3..7],
|
||
|
cur: None,
|
||
|
}
|
||
|
.collect::<Vec<_>>(),
|
||
|
vec![1, 2, 3, 4, 5, 6]
|
||
|
);
|
||
|
}
|
||
|
}
|