Advertisement
nairby

AOC 2023 Day 10

Dec 11th, 2023
1,399
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Rust 5.38 KB | None | 0 0
  1. use std::env;
  2. use std::io::{self, prelude::*, BufReader};
  3. use std::fs::File;
  4. use std::collections::{HashSet,HashMap};
  5.  
  6. use point2d::point2d::Point2D;
  7.  
  8. #[derive(Clone)]
  9. struct Pipe {
  10.     dirs: [Direction; 2]
  11. }
  12. impl Pipe {
  13.     pub fn goes(&self, dir: &Direction) -> bool {
  14.         self.dirs.iter().any(|d| d == dir)
  15.     }
  16. }
  17.  
  18. #[derive(Debug,PartialEq,Copy,Clone)]
  19. enum Direction {
  20.     North,
  21.     South,
  22.     East,
  23.     West,
  24.     Nowhere,
  25. }
  26.  
  27. fn opposite_dir(dir: Direction) -> Direction {
  28.     use Direction::*;
  29.     match dir {
  30.         North => South,
  31.         South => North,
  32.         East  => West,
  33.         West  => East,
  34.         _ => panic!(),
  35.     }
  36. }
  37.  
  38. fn pipe_kind(ch: char) -> Pipe {
  39.     use Direction::*;
  40.     match ch {
  41.         '|' => Pipe { dirs: [North,South] },
  42.         '-' => Pipe { dirs: [East,West] },
  43.         'L' => Pipe { dirs: [North,East] },
  44.         'J' => Pipe { dirs: [North,West] },
  45.         '7' => Pipe { dirs: [South,West] },
  46.         'F' => Pipe { dirs: [South,East] },
  47.         '.' => Pipe { dirs: [Nowhere,Nowhere] },
  48.         'S' => Pipe { dirs: [Nowhere,Nowhere] },
  49.         _ => panic!("Unexpected pipe map character: {ch}"),
  50.     }
  51. }
  52.  
  53. fn move_to(from: &Point2D, dir: &Direction) -> Point2D {
  54.     use Direction::*;
  55.     match dir {
  56.         North => Point2D { x: from.x,     y: from.y - 1 },
  57.         South => Point2D { x: from.x,     y: from.y + 1 },
  58.         East  => Point2D { x: from.x + 1, y: from.y     },
  59.         West  => Point2D { x: from.x - 1, y: from.y     },
  60.         _ => panic!(),
  61.     }
  62. }
  63.  
  64. fn new_dir(dir: Direction, pipe: &Pipe) -> Direction {
  65.     let from = opposite_dir(dir);
  66.     if pipe.dirs[0] == from { pipe.dirs[1] } else { pipe.dirs[0] }
  67. }
  68.  
  69. fn solve(input: &str) -> io::Result<()> {
  70.     let file = File::open(input).expect("Input file not found.");
  71.     let reader = BufReader::new(file);
  72.  
  73.     // The pipe configuration at S is not determined programmatically.
  74.     // Must be specified per input file.
  75.     let start_pipe = match input {
  76.         "input.txt"   => Pipe { dirs: [Direction::South,Direction::East] },
  77.         "sample.txt"  => Pipe { dirs: [Direction::South,Direction::East] },
  78.         "sample2.txt" => Pipe { dirs: [Direction::South,Direction::East] },
  79.         "sample3.txt" => Pipe { dirs: [Direction::South,Direction::East] },
  80.         "sample4.txt" => Pipe { dirs: [Direction::South,Direction::West] },
  81.         _ => panic!("Must specify pipe type at S for each input file."),
  82.     };
  83.  
  84.     // Input
  85.     let input: Vec<String> = match reader.lines().collect() {
  86.         Err(err) => panic!("Unknown error reading input: {err}"),
  87.         Ok(result) => result,
  88.     };
  89.  
  90.     // Build map
  91.     let mut start: Point2D = Point2D { x: -1, y: -1 };
  92.     let mut pipes: HashMap<Point2D,Pipe> = HashMap::new();
  93.     for (y,line) in input.iter().enumerate() {
  94.         for (x,ch) in line.chars().enumerate() {
  95.             let pt = Point2D { x: x as i64, y: y as i64 };
  96.             if ch == 'S' {
  97.                 start = pt;
  98.                 pipes.insert(pt,start_pipe.clone());
  99.             } else {
  100.                 pipes.insert(pt,pipe_kind(ch));
  101.             }
  102.         }
  103.     }
  104.  
  105.     // Trace path and calculate part 1
  106.     let mut steps = 0;
  107.     let mut current = start;
  108.     let mut direction = Direction::East;
  109.     let mut path_map: HashMap<Point2D,Pipe> = HashMap::new();
  110.     path_map.insert(start,start_pipe.clone());
  111.     loop {
  112.         let next_pt = move_to(&current,&direction);
  113.         let pipe_next = pipes.get(&next_pt).unwrap();
  114.         path_map.insert(next_pt,pipe_next.clone());
  115.         direction = new_dir(direction, pipe_next);
  116.         current = next_pt;
  117.         steps += 1;
  118.         if current == start { break }
  119.     }
  120.     println!("Part 1: {}",steps/2); // 6864
  121.  
  122.     // Calculate map extents for part 2
  123.     let xmax = pipes.keys().map(|pt| pt.x).max().unwrap();
  124.     let ymax = pipes.keys().map(|pt| pt.y).max().unwrap();
  125.     let yinf = ymax + 1;
  126.  
  127.     // Part 2
  128.     let mut enclosed_points: HashSet<Point2D> = HashSet::new();
  129.     for x in 0..=xmax {
  130.         'y_lp: for y in 0..=ymax {
  131.            let pt_check = Point2D { x: x, y: y };
  132.  
  133.            // Skip points that are on the path
  134.            if path_map.contains_key(&pt_check) { continue 'y_lp }
  135.  
  136.             // Even-Odd Rule (https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule)
  137.             // Draw vector directly South to infinity (ymax+1) from every point not
  138.             // already part of the path. Count the number of times this vector
  139.             // crosses pipes that go east and pipes that go west.
  140.             // If the minimum of these two counts is odd, point is enclosed.
  141.             let mut crosses_east = 0;
  142.             let mut crosses_west = 0;
  143.             for ynew in y..=yinf {
  144.                 if let Some(pt) = path_map.get(&Point2D { x: x, y: ynew }) {
  145.                     if pt.goes(&Direction::East) { crosses_east += 1 }
  146.                     if pt.goes(&Direction::West) { crosses_west += 1 }
  147.                 }
  148.             }
  149.             // Check for odd number of crosses
  150.             if std::cmp::min(crosses_west,crosses_east) % 2 != 0 {
  151.                 enclosed_points.insert(pt_check);
  152.             }
  153.         }
  154.     }
  155.     let part2 = enclosed_points.len();
  156.     println!("Part 2: {part2}"); // 349
  157.  
  158.     Ok(())
  159. }
  160.  
  161. fn main() {
  162.     let args: Vec<String> = env::args().collect();
  163.     let filename = &args[1];
  164.     solve(&filename).unwrap();
  165. }
  166.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement