From: alex Date: Sun, 10 Dec 2023 14:47:31 +0000 (+0100) Subject: Day10 - part 2 X-Git-Url: https://aoc.elinar.fr/?a=commitdiff_plain;h=da27542d897db9f5f0b2a109fddb7edadfd13599;p=aoc_2023 Day10 - part 2 --- diff --git a/src/day10.rs b/src/day10.rs index 9b30225..1fc4c2d 100644 --- a/src/day10.rs +++ b/src/day10.rs @@ -47,13 +47,13 @@ impl Puzzle { .for_each(|(i,l)| { let mut row: Vec = 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::>() ); + map.push( (0..row.len()).map(|_| ' ').collect::>() ); } map.push(row); @@ -67,17 +67,123 @@ impl Puzzle { }); // ajout d'une dernière ligne - map.push( (0..map[0].len()).map(|_| '.').collect::>() ); + map.push( (0..map[0].len()).map(|_| ' ').collect::>() ); 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::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 = 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::()); + + // ajouter une ligne intermédiaire + // attention à n'ajouter que des éléments de la frontière + let mut intermediary_row: Vec = 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>) { + map.into_iter() + .for_each(|row| { + let s: String = row.into_iter().collect::(); + println!("{}", s); + }); +} + fn run_part1(input: &str) -> Result> { 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> { 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::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>, 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>, map: &Vec>, 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()); } }