day5: reimplement in a sane, fast way
This commit is contained in:
		
							
								
								
									
										142
									
								
								5/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								5/src/main.rs
									
									
									
									
									
								
							@@ -153,16 +153,16 @@ fn main() {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct AlmanacMapping {
 | 
			
		||||
    source_range: Range<u64>,
 | 
			
		||||
    dest_range: Range<u64>,
 | 
			
		||||
    source_range: Range<i64>,
 | 
			
		||||
    dest_range: Range<i64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&str> for AlmanacMapping {
 | 
			
		||||
    fn from(s: &str) -> Self {
 | 
			
		||||
        let s_nums: Vec<_> = s.split_whitespace().take(3).collect();
 | 
			
		||||
        let length: u64 = s_nums[2].parse().unwrap();
 | 
			
		||||
        let source_start: u64 = s_nums[1].parse().unwrap();
 | 
			
		||||
        let dest_start: u64 = s_nums[0].parse().unwrap();
 | 
			
		||||
        let length: i64 = s_nums[2].parse().unwrap();
 | 
			
		||||
        let source_start: i64 = s_nums[1].parse().unwrap();
 | 
			
		||||
        let dest_start: i64 = s_nums[0].parse().unwrap();
 | 
			
		||||
        AlmanacMapping {
 | 
			
		||||
            source_range: source_start..source_start + length,
 | 
			
		||||
            dest_range: dest_start..dest_start + length,
 | 
			
		||||
@@ -171,7 +171,7 @@ impl From<&str> for AlmanacMapping {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AlmanacMapping {
 | 
			
		||||
    fn lookup(&self, key: u64) -> Option<u64> {
 | 
			
		||||
    fn lookup(&self, key: i64) -> Option<i64> {
 | 
			
		||||
        if self.source_range.contains(&key) {
 | 
			
		||||
            Some(self.dest_range.start + (key - self.source_range.start))
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -186,13 +186,74 @@ struct AlmanacMappingTable {
 | 
			
		||||
    mappings: Vec<AlmanacMapping>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn range_overlap<T: num::Num + Ord + Copy>(r1: &Range<T>, r2: &Range<T>) -> Option<Range<T>> {
 | 
			
		||||
    let new_start = std::cmp::max(r1.start, r2.start);
 | 
			
		||||
    let new_end = std::cmp::min(r1.end - T::one(), r2.end - T::one());
 | 
			
		||||
 | 
			
		||||
    if new_start <= std::cmp::min(r1.end - T::one(), r2.end - T::one())
 | 
			
		||||
        && new_end >= std::cmp::max(r1.start, r2.start)
 | 
			
		||||
    {
 | 
			
		||||
        Some(new_start..new_end + T::one())
 | 
			
		||||
    } else {
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn range_exclude<T: num::Num + Ord + Copy>(keep: &Range<T>, exclude: &Range<T>) -> Vec<Range<T>> {
 | 
			
		||||
    let mut residual = Vec::new();
 | 
			
		||||
    if let Some(overlap) = range_overlap(keep, exclude) {
 | 
			
		||||
        if keep.start < overlap.start {
 | 
			
		||||
            residual.push(keep.start..overlap.start);
 | 
			
		||||
        }
 | 
			
		||||
        if keep.end > overlap.end {
 | 
			
		||||
            residual.push(overlap.end..keep.end);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        residual.push(keep.clone());
 | 
			
		||||
    }
 | 
			
		||||
    residual
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AlmanacMappingTable {
 | 
			
		||||
    fn lookup(&self, key: u64) -> u64 {
 | 
			
		||||
    fn lookup(&self, key: i64) -> i64 {
 | 
			
		||||
        self.mappings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .find_map(|map| map.lookup(key))
 | 
			
		||||
            .unwrap_or(key)
 | 
			
		||||
    }
 | 
			
		||||
    fn lookup_ranges(&self, ranges: Vec<Range<i64>>) -> Vec<Range<i64>> {
 | 
			
		||||
        ranges
 | 
			
		||||
            .iter()
 | 
			
		||||
            .flat_map(|range| self.lookup_range(range))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
    fn lookup_range(&self, range: &Range<i64>) -> Vec<Range<i64>> {
 | 
			
		||||
        let mut residual = vec![range.clone()];
 | 
			
		||||
        let mut resolved_mappings = Vec::new();
 | 
			
		||||
 | 
			
		||||
        for map in &self.mappings {
 | 
			
		||||
            let mut local_residual = Vec::new();
 | 
			
		||||
            for range in residual.into_iter() {
 | 
			
		||||
                if let Some(overlap) = range_overlap(&range, &map.source_range) {
 | 
			
		||||
                    let dest_start_idx = overlap.start - map.source_range.start;
 | 
			
		||||
                    let length = overlap.end.saturating_sub(overlap.start);
 | 
			
		||||
                    resolved_mappings.push(
 | 
			
		||||
                        (map.dest_range.start + dest_start_idx)
 | 
			
		||||
                            ..(map.dest_range.start + dest_start_idx + length),
 | 
			
		||||
                    );
 | 
			
		||||
                    local_residual.extend_from_slice(&range_exclude(&range, &overlap));
 | 
			
		||||
                } else {
 | 
			
		||||
                    local_residual.push(range);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            residual = local_residual;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // unresolved maps map 1:1
 | 
			
		||||
        resolved_mappings.extend_from_slice(&residual);
 | 
			
		||||
 | 
			
		||||
        resolved_mappings
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: BufRead> From<&mut Lines<T>> for AlmanacMappingTable {
 | 
			
		||||
@@ -218,7 +279,7 @@ impl<T: BufRead> From<&mut Lines<T>> for AlmanacMappingTable {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct Almanac {
 | 
			
		||||
    seeds: Vec<u64>,
 | 
			
		||||
    seeds: Vec<i64>,
 | 
			
		||||
    seed_to_soil: AlmanacMappingTable,
 | 
			
		||||
    soil_to_fertilizer: AlmanacMappingTable,
 | 
			
		||||
    fertilizer_to_water: AlmanacMappingTable,
 | 
			
		||||
@@ -231,7 +292,7 @@ struct Almanac {
 | 
			
		||||
impl<T: BufRead> From<Lines<T>> for Almanac {
 | 
			
		||||
    fn from(mut lines: Lines<T>) -> Self {
 | 
			
		||||
        let seeds_s = lines.next().unwrap().unwrap();
 | 
			
		||||
        let seeds: Vec<u64> = seeds_s
 | 
			
		||||
        let seeds: Vec<i64> = seeds_s
 | 
			
		||||
            .split_once(": ")
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .1
 | 
			
		||||
@@ -254,7 +315,7 @@ impl<T: BufRead> From<Lines<T>> for Almanac {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Almanac {
 | 
			
		||||
    fn lookup(&self, seed_id: u64) -> u64 {
 | 
			
		||||
    fn lookup(&self, seed_id: i64) -> i64 {
 | 
			
		||||
        self.humidity_to_location.lookup(
 | 
			
		||||
            self.temperature_to_humidity.lookup(
 | 
			
		||||
                self.light_to_temperature.lookup(
 | 
			
		||||
@@ -268,6 +329,20 @@ impl Almanac {
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    fn lookup_range(&self, range: Range<i64>) -> Vec<Range<i64>> {
 | 
			
		||||
        self.humidity_to_location.lookup_ranges(
 | 
			
		||||
            self.temperature_to_humidity.lookup_ranges(
 | 
			
		||||
                self.light_to_temperature.lookup_ranges(
 | 
			
		||||
                    self.water_to_light.lookup_ranges(
 | 
			
		||||
                        self.fertilizer_to_water.lookup_ranges(
 | 
			
		||||
                            self.soil_to_fertilizer
 | 
			
		||||
                                .lookup_ranges(self.seed_to_soil.lookup_ranges(vec![range])),
 | 
			
		||||
                        ),
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PROBLEM 1 solution
 | 
			
		||||
@@ -287,7 +362,7 @@ impl Almanac {
 | 
			
		||||
 | 
			
		||||
// What is the lowest location number that corresponds to any of the initial seed numbers?
 | 
			
		||||
 | 
			
		||||
fn problem1_lowest_location(almanac: &Almanac) -> u64 {
 | 
			
		||||
fn problem1_lowest_location(almanac: &Almanac) -> i64 {
 | 
			
		||||
    almanac
 | 
			
		||||
        .seeds
 | 
			
		||||
        .iter()
 | 
			
		||||
@@ -296,7 +371,7 @@ fn problem1_lowest_location(almanac: &Almanac) -> u64 {
 | 
			
		||||
        .unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
 | 
			
		||||
fn problem1<T: BufRead>(input: Lines<T>) -> i64 {
 | 
			
		||||
    let almanac = Almanac::from(input);
 | 
			
		||||
    problem1_lowest_location(&almanac)
 | 
			
		||||
}
 | 
			
		||||
@@ -327,33 +402,40 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
 | 
			
		||||
// the almanac. What is the lowest location number that corresponds to any of the
 | 
			
		||||
// initial seed numbers?
 | 
			
		||||
 | 
			
		||||
fn problem2_lowest_location(almanac: &Almanac) -> u64 {
 | 
			
		||||
fn problem2_lowest_location(almanac: &Almanac) -> i64 {
 | 
			
		||||
    let seed_ranges = almanac
 | 
			
		||||
        .seeds
 | 
			
		||||
        .iter()
 | 
			
		||||
        .tuples()
 | 
			
		||||
        .map(|(range_start, length)| *range_start..*range_start + *length);
 | 
			
		||||
 | 
			
		||||
    seed_ranges
 | 
			
		||||
        .map(|range| {
 | 
			
		||||
            range
 | 
			
		||||
                .into_par_iter()
 | 
			
		||||
                .map(|x| almanac.lookup(x))
 | 
			
		||||
                .min()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
        })
 | 
			
		||||
        .min()
 | 
			
		||||
        .unwrap()
 | 
			
		||||
    // seed_ranges
 | 
			
		||||
    //     .map(|range| {
 | 
			
		||||
    //         range
 | 
			
		||||
    //             .into_par_iter()
 | 
			
		||||
    //             .map(|x| almanac.lookup(x))
 | 
			
		||||
    //             .min()
 | 
			
		||||
    //             .unwrap()
 | 
			
		||||
    //     })
 | 
			
		||||
    //     .min()
 | 
			
		||||
    //     .unwrap()
 | 
			
		||||
    let ranges = seed_ranges
 | 
			
		||||
        .flat_map(|range| almanac.lookup_range(range))
 | 
			
		||||
        .collect_vec();
 | 
			
		||||
 | 
			
		||||
    ranges.iter().map(|range| range.start).min().unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
 | 
			
		||||
fn problem2<T: BufRead>(input: Lines<T>) -> i64 {
 | 
			
		||||
    let almanac = Almanac::from(input);
 | 
			
		||||
    problem2_lowest_location(&almanac)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use num::Num;
 | 
			
		||||
    use std::io::{BufRead, Cursor};
 | 
			
		||||
    use test_case::test_case;
 | 
			
		||||
 | 
			
		||||
    use crate::*;
 | 
			
		||||
 | 
			
		||||
@@ -413,6 +495,18 @@ humidity-to-location map:
 | 
			
		||||
        assert_eq!(a.lookup(79), 82);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test_case(0..5, 2..4, Some(2..4) ; "one range overlaps completely")]
 | 
			
		||||
    #[test_case(0..5, 2..10, Some(2..5) ; "one range overlaps from below")]
 | 
			
		||||
    #[test_case(0..5, 5..10, None ; "no overlap, touching")]
 | 
			
		||||
    #[test_case(0..5, 6..10, None ; "no overlap, disjoint")]
 | 
			
		||||
    fn test_range_overlap<T: Num + Copy + Ord + std::fmt::Debug>(
 | 
			
		||||
        r1: Range<T>,
 | 
			
		||||
        r2: Range<T>,
 | 
			
		||||
        expected: Option<Range<T>>,
 | 
			
		||||
    ) {
 | 
			
		||||
        assert_eq!(range_overlap(&r1, &r2), expected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_problem1_example() {
 | 
			
		||||
        let c = Cursor::new(EXAMPLE_ALMANAC);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user