this post was submitted on 18 Dec 2023
13 points (93.3% liked)

Advent Of Code

770 readers
1 users here now

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2023

Solution Threads

M T W T F S S
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

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 1 year ago
MODERATORS
13
submitted 11 months ago* (last edited 11 months ago) by CameronDev to c/advent_of_code
 

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

you are viewing a single comment's thread
view the rest of the comments
[โ€“] [email protected] 2 points 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!