AoC23/src/day05.rs

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]
);
}
}