Zytkowski's Thought Dumpster

Advent of Code 2023 - Day 05

I'ts that time of year again, the Advent of Code! I'm completing the challenges this year using Rust 🦀 Day 5 was a very memory intensive day 😋!

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 5 Challenge

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

Solution for Part 1

use std::cmp::Ordering;
use std::fs::File;
use std::io::Read;
use std::io::Result;
use std::path::Path;
use std::str::FromStr;

struct Mapping {
    dest_range_start: i64,
    source_range_start: i64,
    range_length: i64,
}

fn main() {
    let input_file_path = "path_to_input_file";
    let file_content = read_file(input_file_path)
        .unwrap_or_else(|_| panic!("Could not open {input_file_path}"));
    let file_sections: Vec<&str> = file_content.split("\r\n\r\n").collect();
    let seeds = &file_sections[0];
    let sections = &file_sections[1..];
    let seeds: Vec<&str> = if let Some((_, seeds_as_str)) = seeds.split_once(' ') {
        seeds_as_str.split(' ').collect()
    } else {
        panic!("Seed header was empty")
    };
    let mut mappings: Vec<Vec<Mapping>> = Vec::new();
    for section in sections.iter() {
        let (_, map) = section.split_once("\r\n").unwrap();
        let mut inner_vec : Vec<Mapping> = Vec::new();
        for line in map.split("\r\n") {
            let range_definition = line.split(' ')
                .map(|i| {
                    i64::from_str(i).unwrap()
                })
                .collect::<Vec<i64>>();
            let range_definition: &[i64] = range_definition.as_slice();
            inner_vec.push(
                Mapping {
                    dest_range_start: range_definition[0],
                    source_range_start: range_definition[1],
                    range_length: range_definition[2],
                }
            )
        }
        mappings.push(inner_vec);
    }
    for map in mappings.iter_mut() {
        map.sort_by(|a, b| {
            if b.source_range_start < a.source_range_start {
                Ordering::Greater
            } else {
                Ordering::Less
            }
        });
    }
    let mut lowest_location_number = 0;
    for seed in seeds {
        let mut current_input = i64::from_str(seed).unwrap();
        for map in mappings.as_slice() {
            let adequate_mapping = map.iter().find(|i| {
                (i.source_range_start..i.source_range_start + i.range_length).contains(&current_input)
            });
            match adequate_mapping {
                None => { continue }
                Some(mapping) => {
                    current_input = (current_input - mapping.source_range_start) + mapping.dest_range_start
                }
            }
        }
        if lowest_location_number == 0 || lowest_location_number > current_input {
            lowest_location_number = current_input;
        }
    }
    println!("Lowest location number: {}", lowest_location_number);
}

fn read_file<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_as_str).unwrap();
    Ok(file_as_str)
}

Solution for Part 2

use std::cmp::Ordering;
use std::fs::File;
use std::io::Read;
use std::io::Result;
use std::path::Path;
use std::str::FromStr;
use std::time::SystemTime;

struct Mapping {
    dest_range_start: i64,
    source_range_start: i64,
    range_length: i64,
}

fn main() {
    let curr_time = SystemTime::now();
    let input_file_path = "path_to_input_file";
    let file_content = read_file(input_file_path)
        .unwrap_or_else(|_| panic!("Could not open {input_file_path}"));
    let file_sections: Vec<&str> = file_content.split("\r\n\r\n").collect();
    let seeds = &file_sections[0];
    let sections = &file_sections[1..];
    let seed_ranges: Vec<(i64, i64)> = if let Some((_, seeds_as_str)) = seeds.split_once(' ') {
        let mut result: Vec<(i64, i64)> = Vec::new();
        let seed_initial_parse = seeds_as_str.split(' ')
            .map(|i| i64::from_str(i).unwrap())
            .collect::<Vec<i64>>();
        for range_identifier in seed_initial_parse.chunks(2) {
            result.push((range_identifier[0], range_identifier[1]));
        }
        result
    } else {
        panic!("Seed header was empty")
    };
    let mut mappings: Vec<Vec<Mapping>> = Vec::new();
    for section in sections.iter() {
        let (_, map) = section.split_once("\r\n").unwrap();
        let mut inner_vec: Vec<Mapping> = Vec::new();
        for line in map.split("\r\n") {
            let range_definition = line.split(' ')
                .map(|i| {
                    i64::from_str(i).unwrap()
                })
                .collect::<Vec<i64>>();
            let range_definition: &[i64] = range_definition.as_slice();
            inner_vec.push(
                Mapping {
                    dest_range_start: range_definition[0],
                    source_range_start: range_definition[1],
                    range_length: range_definition[2],
                }
            )
        }
        mappings.push(inner_vec);
    }
    for map in mappings.iter_mut() {
        map.sort_by(|a, b| {
            if b.source_range_start < a.source_range_start {
                Ordering::Greater
            } else {
                Ordering::Less
            }
        });
    }
    let mut lowest_location_number = 0;
    for (seed_base, seed_range) in seed_ranges {
        for seed in seed_base..seed_base+seed_range {
            let result = process_seed(seed, &mappings);
            if lowest_location_number == 0 || lowest_location_number > result {
                lowest_location_number = result;
            }
        }
    }
    println!("Lowest location number: {}", lowest_location_number);
    let elapsed = curr_time.elapsed().unwrap();
    println!("Execution time: {} ms", elapsed.as_millis())
}

fn process_seed(seed: i64, mappings: &Vec<Vec<Mapping>>) -> i64 {
    let mut current_input = seed;
    for map in mappings.as_slice() {
        let adequate_mapping = map.iter().find(|i| {
            (i.source_range_start..i.source_range_start + i.range_length).contains(&current_input)
        });
        match adequate_mapping {
            None => { continue; }
            Some(mapping) => {
                current_input = (current_input - mapping.source_range_start) + mapping.dest_range_start
            }
        }
    }
    current_input
}

fn read_file<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_as_str).unwrap();
    Ok(file_as_str)
}