Zytkowski's Thought Dumpster

Advent of Code 2023 - Day 08

I'ts that time of year again, the Advent of Code! I'm completing the challenges this year using Rust 🦀 The loop on day 8 can really get you!

I'll be posting them here, just to document how I'm solving each challenge, and I highly encourage you to check them out before reading my solutions!

Here's the Day 8 Challenge

BTW: Complete Part 1 to have access to Part 2 😉

Solution for Part 1

use std::fs::File;
use std::io::{Read, Result};
use std::path::Path;

#[derive(Debug, Clone)]
struct NodePath {
    id: String,
    left: String,
    right: String,
}


fn main() {
    let input_file_path = "path_to_input_file";
    let mut curr_sum = 0;
    let file = file_as_string(input_file_path)
        .unwrap_or_else(|_| panic!("Could not find input file {}", input_file_path));
    let (instructions, path) = file.split_once("\r\n\r\n").unwrap();
    let instructions: Vec<char> = instructions.chars().collect();
    let mut instruction_idx = 0;
    let mut path: Vec<&str> = path.split("\r\n").collect();
    path.sort();
    let path: Vec<NodePath> = path.iter().map(|str| parse_node_path(str)).collect();
    let mut curr_node = path.get(0).unwrap();
    while curr_node.id != "ZZZ" {
        curr_sum += 1;
        curr_node = match instructions.get(instruction_idx).unwrap() {
            'L' => path.iter().find(|n| n.id == curr_node.left).unwrap(),
            'R' => path.iter().find(|n| n.id == curr_node.right).unwrap(),
            _ => panic!("Dead end on directions.")
        };
        instruction_idx = if instruction_idx + 1 == instructions.len() {
            0
        } else {
            instruction_idx + 1
        }
    }
    println!("Steps to get to ZZZ: {}", curr_sum);
}

fn parse_node_path(raw_node_path: &str) -> NodePath {
    let raw_node_path = raw_node_path.replace([' ', '(', ')'], "");
    let (id, dirs) = raw_node_path.split_once('=').unwrap();
    let (left, right) = dirs.split_once(',').unwrap();
    NodePath {
        id: id.into(),
        left: left.into(),
        right: right.into(),
    }
}

fn file_as_string<P: AsRef<Path>>(filename: P) -> Result<String> {
    let mut file = File::open(filename)?;
    let mut file_as_str = String::new();
    File::read_to_string(&mut file, &mut file_as_str)?;
    Ok(file_as_str)
}

Solution for Part 2

use std::fs::File;
use std::io::{Read, Result};
use std::path::Path;

#[derive(Debug, Clone)]
struct NodePath {
    id: String,
    left: String,
    right: String,
}

fn gcd(a: usize, b: usize) -> usize {
    if b == 0 {
        a
    } else {
        gcd(b, a % b)
    }
}

fn lcm(a: usize, b: usize) -> usize {
    a / gcd(a, b) * b
}

fn main() {
    let input_file_path = "path_to_input";
    let mut curr_sum = 0;
    let file = file_as_string(input_file_path)
        .unwrap_or_else(|_| panic!("Could not find input file {}", input_file_path));
    let (instructions, path) = file.split_once("\r\n\r\n").unwrap();
    let instructions: Vec<char> = instructions.chars().collect();
    let mut instruction_idx = 0;
    let mut path: Vec<&str> = path.split("\r\n").collect();
    path.sort();
    let path: Vec<NodePath> = path.iter()
        .map(|str| parse_node_path(str))
        .collect();
    let mut walking_nodes: Vec<usize> = vec![];
    for (idx, node) in path.iter().enumerate() {
        if node.id.ends_with('A') {
            walking_nodes.push(idx);
        }
    }
    let mut finishing_indexes : Vec<usize> = vec![];
    while !walking_nodes.iter().all(|n| path.get(*n).unwrap().id.ends_with('Z')) {
        curr_sum += 1;
        let original_len = walking_nodes.len();
        walking_nodes = process_walking_nodes(&path, walking_nodes, instructions[instruction_idx]);
        instruction_idx = if instruction_idx + 1 == instructions.len() {
            0
        } else {
            instruction_idx + 1
        };
        if original_len != walking_nodes.len() {
            finishing_indexes.push(curr_sum as usize);
        }
    }

   let lcm = finishing_indexes.iter()
       .fold(1,|x, a| lcm(x, *a));

    println!("Steps to where all nodes end with Z: {}", lcm);
}

fn process_walking_nodes(
    path: &[NodePath],
    walking_nodes: Vec<usize>,
    instruction: char,
) -> Vec<usize> {
    let mut new_walking_nodes: Vec<usize> = vec![];
    for node in walking_nodes {
        let next_node_id = match instruction {
            'L' => { &path[node].left }
            'R' => { &path[node].right }
            _ => { panic!("Error whilst traversing nodes") }
        };
        if next_node_id.ends_with('Z') {
            continue;
        }
        let next_node_index = path.iter().position(|n| &n.id == next_node_id).unwrap();
        new_walking_nodes.push(next_node_index)
    }
    new_walking_nodes
}

fn parse_node_path(raw_node_path: &str) -> NodePath {
    let raw_node_path = raw_node_path.replace([' ', '(', ')'], "");
    let (id, dirs) = raw_node_path.split_once('=').unwrap();
    let (left, right) = dirs.split_once(',').unwrap();
    NodePath {
        id: id.into(),
        left: left.into(),
        right: right.into(),
    }
}

fn file_as_string<P: AsRef<Path>>(filename: P) -> Result<String> {
    let mut file = File::open(filename)?;
    let mut file_as_str = String::new();
    File::read_to_string(&mut file, &mut file_as_str)?;
    Ok(file_as_str)
}