summaryrefslogtreecommitdiff
path: root/src/bin/day9.rs
blob: ecc95ae6b39c2bb4837366de71d697d1b3ebb14c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::collections::HashSet;

fn main() {
    let input = advent::read_lines(9);
    println!("9a: {}", risk_levels_low(&input));
    println!("9b: {}", largest_basins(&input));
}

#[derive(Clone,Copy,PartialEq,Eq,Hash)]
struct Position {
    x: usize,
    y: usize,
}

struct Heightmap {
    map: Vec<Vec<u8>>,
}

impl Heightmap {
    fn new<T: AsRef<str>>(input: &[T]) -> Heightmap {
        let map : Vec<Vec<u8>> = input.iter()
                                      .map(|line| line.as_ref().split_terminator("").skip(1).map(|c| c.parse::<u8>().unwrap()).collect())
                                      .collect();
        Heightmap { map }
    }

    fn find_low_points(&self) -> Vec<Position> {
        let mut low_points = Vec::new();
        for y in 0 .. self.map.len() {
            for x in 0 .. self.map[0].len() {
                let current = self.map[y][x];
                if x > 0 && self.map[y][x-1] <= current {
                    continue;
                }
                if x < self.map[y].len() - 1 && self.map[y][x+1] <= current {
                    continue;
                }
                if y > 0 && self.map[y-1][x] <= current {
                    continue;
                }
                if y < self.map.len() - 1 && self.map[y+1][x] <= current {
                    continue;
                }
                low_points.push(Position { x, y });
            }
        }
        low_points
    }

    fn risk_level_at(&self, pos: Position) -> u8 {
        self.map[pos.y][pos.x] + 1
    }

    fn basin_at(&self, pos: Position) -> HashSet<Position> {
        let mut points = HashSet::from([pos]);

        loop {
            let mut new_points = points.clone();
            for pos in &points {
                if pos.x > 0 && self.map[pos.y][pos.x-1] < 9 {
                    new_points.insert(Position { x: pos.x - 1, y: pos.y });
                }
                if pos.x < self.map[pos.y].len() - 1 && self.map[pos.y][pos.x+1] < 9 {
                    new_points.insert(Position { x: pos.x + 1, y: pos.y });
                }
                if pos.y > 0 && self.map[pos.y-1][pos.x] < 9 {
                    new_points.insert(Position { x: pos.x, y: pos.y - 1});
                }
                if pos.y < self.map.len() - 1 && self.map[pos.y+1][pos.x] < 9 {
                    new_points.insert(Position { x: pos.x, y: pos.y + 1});
                }
            }
            if points.len() == new_points.len() {
                break;
            }
            points = new_points;
        }
        points
    }
}

fn risk_levels_low<T: AsRef<str>>(input: &[T]) -> usize {
    let heightmap = Heightmap::new(input);

    heightmap.find_low_points()
             .iter()
             .map(|&pos| heightmap.risk_level_at(pos) as usize)
             .sum()
}

fn largest_basins<T: AsRef<str>>(input: &[T]) -> usize {
    let heightmap = Heightmap::new(input);
    let lowpoints = heightmap.find_low_points();

    let mut basin_sizes : Vec<usize> = lowpoints.iter()
                                                .map(|&pos| heightmap.basin_at(pos))
                                                .map(|basin| basin.len())
                                                .collect();
    basin_sizes.sort_unstable();
    basin_sizes.iter()
               .rev()
               .take(3)
               .product()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let input = [
            "2199943210",
            "3987894921",
            "9856789892",
            "8767896789",
            "9899965678",
        ];
        assert_eq!(risk_levels_low(&input), 15);
        assert_eq!(largest_basins(&input), 1134);
    }
}