]> aoc.elinar.fr Git - aoc_2023/commitdiff
Day10 - part 2
authoralex <null>
Sun, 10 Dec 2023 14:47:31 +0000 (15:47 +0100)
committeralex <null>
Sun, 10 Dec 2023 14:47:31 +0000 (15:47 +0100)
src/day10.rs

index 9b30225be880082085531c5e061dc6733ec0ef60..1fc4c2d1560d30e0af7a8dedac8f43a398e4412e 100644 (file)
@@ -47,13 +47,13 @@ impl Puzzle {
             .for_each(|(i,l)| {
                 let mut row: Vec<char> = Vec::new();
 
-                row.push('.');
+                row.push(' ');
                 l.chars().for_each(|c| { row.push(c); });
-                row.push('.');
+                row.push(' ');
 
                 // ajout d'une première ligne de '.'
                 if i == 0 {
-                    map.push( (0..row.len()).map(|_| '.').collect::<Vec<char>>() );
+                    map.push( (0..row.len()).map(|_| ' ').collect::<Vec<char>>() );
                 }
                 map.push(row);
 
@@ -67,17 +67,123 @@ impl Puzzle {
             });
 
         // ajout d'une dernière ligne
-        map.push( (0..map[0].len()).map(|_| '.').collect::<Vec<char>>() );
+        map.push( (0..map[0].len()).map(|_| ' ').collect::<Vec<char>>() );
 
         Self {
             start,
-            map
+            map,
         }
     }
 
     pub fn get_char(self: &Self, pos: (usize, usize)) -> char {
         self.map[pos.0][pos.1]
     }
+
+    pub fn frontier_coord(self: &Self) -> Vec<(usize, usize)> {
+        let mut current = self.start;
+        let (row, col) = current;
+        // on recherche dans les 4 directions
+        let start = [
+            (row - 1, col),
+            (row + 1, col),
+            (row, col - 1),
+            (row, col + 1),
+        ];
+
+        let mut frontier_coord: Vec<(usize, usize)> = Vec::new();
+        for mut next in start {
+            frontier_coord = Vec::new();
+            while self.get_char(next) != 'S' && is_connected(self.get_char(next), current, next) {
+                frontier_coord.push((current.0, current.1));
+                let next_next = get_next_next(self.get_char(next), current, next);
+                current = next;
+                next = next_next;
+            }
+            if self.get_char(next) == 'S' {
+                frontier_coord.push((current.0, current.1));
+                break;
+            }
+        }
+
+        frontier_coord
+    }
+
+    // crée une nouvelle carte agrandie en ajoutant un caractère intermédiaire
+    // pour "décoller" les tuyaux qui se toucheraient
+    // et ainsi permettre de toujours trouver soit
+    //   - un chemin qui amène au bord
+    //   - pas de chemin (et donc ensemble fermé)
+    pub fn extend(self: &Self) -> Self {
+        let mut new_map: Vec<Vec<char>> = Vec::new();
+        let mut new_start = (0, 0);
+
+        let frontier_coord = self.frontier_coord();
+
+        for row in 0..self.map.len() {
+            let mut new_row: Vec<char> = Vec::new();
+            // ajout d'une colonne intemédiaire
+            // attention à n'ajouter que des éléments de la frontière
+            self.map[row].iter()
+                .enumerate()
+                .for_each(|(col, &c)| {
+                    let mut new_c = match c {
+                        'S' => {
+                            let right_char = self.map[self.start.0][self.start.1 + 1];
+                            match right_char {
+                                '-' | 'j' | '7' => '-',
+                                '|' | 'L' | 'F' | '.' => ' ',
+                                _ => unreachable!(),
+                            }
+                        }
+                        '-' | 'L' | 'F' => '-',
+                        '|' | 'J' | '7' | '.' | ' ' => ' ',
+                        _ => unreachable!(),
+                    };
+                    if !is_frontier(&frontier_coord, (row, col)) {
+                        new_c = ' ';
+                    }
+                    new_row.push(c);
+                    new_row.push(new_c);
+                });
+            new_map.push(new_row.clone());
+            //println!("row[{:3}]: {}", row, new_row.iter().collect::<String>());
+
+            // ajouter une ligne intermédiaire
+            // attention à n'ajouter que des éléments de la frontière
+            let mut intermediary_row: Vec<char> = Vec::new();
+            new_row.iter()
+                .enumerate()
+                .for_each(|(col, &c)| {
+                    let new_c = match c {
+                        'S' => {
+                            new_start = (2 * row, col);
+                            let bottom_char = self.map[self.start.0 + 1][self.start.1];
+                            match bottom_char {
+                                '|' | 'L' | 'J' => '|',
+                                '-' | '7' | 'F' | '.' => ' ',
+                                _ => unreachable!(),
+                            }
+                        },
+                        '|' | '7' | 'F' => {
+                            if is_frontier(&frontier_coord, (row, col / 2)) {
+                                '|'
+                            } else {
+                                ' '
+                            }
+                        }
+                        '-' | 'L' | 'J' | '.' | ' ' => ' ',
+                        _ => unreachable!(),
+                    };
+                    intermediary_row.push(new_c);
+                });
+            new_map.push(intermediary_row);
+        }
+
+        Self {
+            map: new_map,
+            start: new_start,
+        }
+    }
 }
 
 // a -> b
@@ -90,11 +196,15 @@ fn is_connected(c: char, a: (usize, usize), b: (usize, usize)) -> bool {
         'J' => (a.1 == b.1 && (a.0 + 1) == b.0) || (a.0 == b.0 && (a.1 + 1) == b.1),
         '7' => (a.1 == b.1 && a.0 == (b.0 + 1)) || (a.0 == b.0 && (a.1 + 1) == b.1),
         'F' => (a.1 == b.1 && a.0 == (b.0 + 1)) || (a.0 == b.0 && a.1 == (b.1 + 1)),
-        '.' => false,
+        '.' | ' ' => false,
         _ => unreachable!(),
     }
 }
 
+fn is_frontier(frontier: &Vec<(usize, usize)>, x: (usize, usize)) -> bool {
+    frontier.contains(&x)
+}
+
 // besoin de connaitre le sens
 // c le char du tuyau de connexion
 // current: la position actuelle
@@ -165,46 +275,134 @@ fn get_next_next(c: char, current: (usize, usize), pipe: (usize, usize) ) -> (us
             }
         },
 
-        '.' | 'S' => (0, 0),
+        '.' | 'S' | ' ' => (0, 0),
         _ => unreachable!(),
     }
 }
 
+fn print_map(map: &Vec<Vec<char>>) {
+    map.into_iter()
+        .for_each(|row| {
+            let s: String = row.into_iter().collect::<String>();
+            println!("{}", s);
+        });
+}
+
 fn run_part1(input: &str) -> Result<u32, Box<dyn Error>> {
     println!("Running day10 - part 1");
 
     let puzzle = Puzzle::new(input);
 
-    let mut current = puzzle.start;
-    let (row, col) = current;
-    // on recherche dans les 4 directions
-    let start = [
-        (row - 1, col),
-        (row + 1, col),
-        (row, col - 1),
-        (row, col + 1),
-    ];
-    let mut count: u32 = 0;
-    for mut next in start {
-        count = 1;
-        while puzzle.get_char(next) != 'S' && is_connected(puzzle.get_char(next), current, next) {
-            count += 1;
-            let next_next = get_next_next(puzzle.get_char(next), current, next);
-            current = next;
-            next = next_next;
-        }
-        if puzzle.get_char(next) == 'S' {
-            break;
-        }
-    }
-
-    let res = count.div_ceil(2);
+    let res = (puzzle.frontier_coord().len() as u32).div_ceil(2);
     Ok(res)
 }
 
 fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
     println!("Running day10 - part 2");
-    let res = 0;
+
+    let puzzle = Puzzle::new(input);
+    let puzzle_frontier_length = puzzle.frontier_coord().len() as u32;
+
+    // pour chaque point qui n'est pas la frontière
+    //   si on atteint la bordure dans au moins 1 des 4 directions, alors
+    //      ce point est à l'exterieur
+    //   sinon 2 cas
+    //      - cas de bordures qui se touchent, mais le point à à l'exterieur de
+    //        l'ensemble
+    //      - cas où le point à à l'intérieur de la partie fermée => OK
+    //
+    //  comment déterminer si un point est à l'extérieur de la partie fermée
+    //  et en particulier du fait que 2 tuyaux se touchent ?
+    //
+    //  déterminer qu'un point est à l'extérieur
+    //    - on atteint la bordure sans traverser la frontière
+
+
+    // idée: modifier le pattern pour ajouter les interstices
+    // comme cela il n'y aura plus qu'à touver un chemin vers une bordure ou non
+    // attention cela ajoute de la complexité car des formes ne sont pas à compter
+    // utiliser un caractère non défini pour ne pas avoir à le compter (par ex un espace ' ')
+
+    //print_map(&puzzle.map);
+
+    let puzzle_extended = puzzle.extend();
+    let map_extended = &puzzle_extended.map;
+    //println!("new start: {:?}", &puzzle_extended.start);
+    //print_map(map_extended);
+
+    // pour chaque case qui n'est pas sur la frontière, on regarde si on peut
+    // atteindre le bord en partant d'elle sans avoir à passer par la frontière
+    // si c'est le cas, la case est à l'extérieur
+    // sinon elle est à l'intérieur
+
+    // on check que la case n'a pas déjà été visitée
+    let mut seen: Vec<Vec<bool>> = Vec::new();
+    map_extended.iter()
+        .for_each(|row| {
+            seen.push(vec![false; row.len()]);
+        });
+
+    // récupérer les coordonnées de la frontière de la boucle
+    let frontier_coord = puzzle_extended.frontier_coord();
+
+    fn is_border(map: &Vec<Vec<char>>, x: (usize, usize)) -> bool {
+        x.0 == 0 || x.1 == 0 || x.0 == map.len() - 1 || x.1 == map[0].len()
+    }
+
+    // prendre un point non encore visité
+    // chercher la bordure
+    //
+    // avec l'agrandissement on devrait avoir exactement 3 ensembles disjoints
+    //   - la frontière
+    //   - l'extérieur
+    //   - l'intérieur
+    //
+
+    // on retire de "seen" la bordure fictive ajoutée autour du puzzle
+    (0..map_extended.len()).for_each(|i| {
+        seen[i][0] = true;
+        seen[i][map_extended[0].len() - 1] = true;
+    });
+    (0..seen[0].len()).for_each(|i| {
+        seen[0][i] = true;
+        seen[map_extended.len() - 1][i] = true;
+    });
+
+    // on part de start et on visite tous les nœuds adjacents en dehors de la frontière
+    // on compte le nombre de nœuds (d'origine) visité
+    fn travel((r, c): (usize, usize), seen: &mut Vec<Vec<bool>>, map: &Vec<Vec<char>>, frontier: &Vec<(usize, usize)>) -> u32 {
+        if is_border(map, (r, c)) || is_frontier(frontier, (r,c)) {
+            return 0;
+        }
+        seen[r][c] = true;
+        let mut count: u32 = match map[r][c] {
+            ' ' => 0,
+            _ => 1,
+        };
+
+        // on va dans les 4 directions
+        if !seen[r - 1][c] {
+            count += travel((r - 1, c), seen, map, frontier);
+        }
+        if !seen[r + 1][c] {
+            count += travel((r + 1, c), seen, map, frontier);
+        }
+        if !seen[r][c - 1] {
+            count += travel((r, c - 1), seen, map, frontier);
+        }
+        if !seen[r][c + 1] {
+            count += travel((r, c + 1), seen, map, frontier);
+        }
+        count
+    }
+
+    let mut start = (1, 1);
+    let count_outside = travel(start, &mut seen, map_extended, &frontier_coord);
+
+    // penser à retirer 2 lignes et 2 colonnes (les bordures en plus)
+    let total: u32 = (puzzle.map[0].len() - 2) as u32 * (puzzle.map.len() - 2) as u32;
+
+    let res = total - puzzle_frontier_length - count_outside;
     Ok(res)
 }
 
@@ -268,9 +466,68 @@ LJ.LJ";
     }
 
     #[test]
-    fn day10_part2() {
-        let input = "";
+    fn day10_part2_example1() {
+        let input = "\
+...........
+.S-------7.
+.|F-----7|.
+.||.....||.
+.||.....||.
+.|L-7.F-J|.
+.|..|.|..|.
+.L--J.L--J.
+...........";
+        let res = run_part2(&input);
+        assert_eq!(4, res.unwrap());
+    }
+
+    #[test]
+    fn day10_part2_example2() {
+        let input = "\
+..........
+.S------7.
+.|F----7|.
+.||....||.
+.||....||.
+.|L-7F-J|.
+.|..||..|.
+.L--JL--J.
+..........";
+        let res = run_part2(&input);
+        assert_eq!(4, res.unwrap());
+    }
+
+    #[test]
+    fn day10_part2_example3() {
+        let input = "\
+.F----7F7F7F7F-7....
+.|F--7||||||||FJ....
+.||.FJ||||||||L7....
+FJL7L7LJLJ||LJ.L-7..
+L--J.L7...LJS7F-7L7.
+....F-J..F7FJ|L7L7L7
+....L7.F7||L7|.L7L7|
+.....|FJLJ|FJ|F7|.LJ
+....FJL-7.||.||||...
+....L---J.LJ.LJLJ...";
+        let res = run_part2(&input);
+        assert_eq!(8, res.unwrap());
+    }
+
+    #[test]
+    fn day10_part2_example4() {
+        let input = "\
+FF7FSF7F7F7F7F7F---7
+L|LJ||||||||||||F--J
+FL-7LJLJ||||||LJL-77
+F--JF--7||LJLJ7F7FJ-
+L---JF-JLJ.||-FJLJJ7
+|F|F-JF---7F7-L7L|7|
+|FFJF7L7F-JF7|JL---7
+7-L-JL7||F7|L7F-7F7|
+L.L7LFJ|||||FJL7||LJ
+L7JLJL-JLJLJL--JLJ.L";
         let res = run_part2(&input);
-        assert_eq!(0, res.unwrap());
+        assert_eq!(10, res.unwrap());
     }
 }