Day 18: Lavaduct Lagoon

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • abclop99@beehaw.org
    link
    fedilink
    arrow-up
    2
    ·
    11 months ago

    Rust

    Code
    use std::fs;
    use std::path::PathBuf;
    
    use clap::Parser;
    
    #[derive(Parser)]
    #[command(author, version, about, long_about = None)]
    struct Cli {
        /// A file containing the input
        input_file: PathBuf,
    
        #[arg(short, long)]
        part: Option,
    }
    
    fn main() {
        // Parse CLI arguments
        let cli = Cli::parse();
    
        // Read file
        let input_text = fs::read_to_string(&cli.input_file)
            .expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());
    
        let (run_part_1, run_part_2) = if let Some(part) = cli.part {
            match part {
                1 => (true, false),
                2 => (false, true),
                _ => unimplemented!(),
            }
        } else {
            (true, true)
        };
        let (it1, it2) = preprocess(&input_text);
    
        if run_part_1 {
            let solution = solve(it1);
            println!("Part 1: {solution}");
        }
        if run_part_2 {
            let solution = solve(it2);
            println!("Part 2: {solution}");
        }
    }
    
    /// Preprocessing for solving
    /// Extracts important information from the input
    /// `part` specifies which part to preprocess for.
    /// Returns a vector for each part containing a direction and amount
    fn preprocess(input: &str) -> (Vec<((isize, isize), isize)>, Vec<((isize, isize), isize)>) {
        let it = input.lines().map(|l| {
            let line: Vec<_> = l.split(' ').collect();
            let direction: char = line[0].chars().nth(0).unwrap();
            let amount: isize = line[1].parse().unwrap();
            let color: &str = &line[2][2..8];
    
            let direction = match direction {
                'R' => (1, 0),
                'L' => (-1, 0),
                'U' => (0, 1),
                'D' => (0, -1),
                _ => unreachable!(),
            };
    
            ((direction, amount), {
                let amount: isize = isize::from_str_radix(&color[..5], 16).unwrap();
                let direction = match &color[5..] {
                    "0" => (1, 0),
                    "1" => (0, -1),
                    "2" => (-1, 0),
                    "3" => (0, 1),
                    _ => unreachable!(),
                };
                (direction, amount)
            })
        });
    
        let it1 = it.clone().map(|(i1, _i2)| i1).collect();
        let it2 = it.map(|(_i1, i2)| i2).collect();
    
        (it1, it2)
    }
    
    /// Finds the area using the shoelace formula
    fn solve(it: Vec<((isize, isize), isize)>) -> isize {
        // Get coordinates from it
        let mut coords: Vec<(isize, isize)> = Vec::with_capacity(it.len() + 1);
    
        // Start at (0, 0)
        coords.push((0, 0)); // Needs to be at both begining and end
        let (mut x, mut y) = (0, 0);
    
        let mut perimeter_length = 0;
    
        // Compute and add the coords
        for (direction, amount) in it {
            let translation = (direction.0 * amount, direction.1 * amount);
            x += translation.0;
            y += translation.1;
    
            coords.push((x, y));
            perimeter_length += amount;
        }
    
        // Should end where it started
        assert_eq!(coords.last().unwrap(), &(0, 0));
    
        // Shoelace formula
        let a = coords
            .iter()
            .zip(coords.iter().skip(1))
            .map(|((x1, y1), (x2, y2))| (x1 * y2) - (x2 * y1))
            .sum::()
            / 2;
    
        // Found by drawing, then trial and error
        // Shoelace area missing 1/2 of perimeter
        // Inside and outside corners cancel out except for one
        a.abs() + perimeter_length / 2 + 1
    }
    
    Yay math!