126 lines
3.8 KiB
Rust
126 lines
3.8 KiB
Rust
use std::fs::File;
|
|
use std::io::{self, BufRead};
|
|
use std::path::Path;
|
|
use std::str::FromStr;
|
|
|
|
// Define type aliases for clarity
|
|
type SeedNumber = (i32, i32);
|
|
type CategoryItem = (i32, i32, i32);
|
|
type SeedNumbers = Vec<SeedNumber>;
|
|
type Category = Vec<CategoryItem>;
|
|
type Categories = Vec<Category>;
|
|
|
|
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
let file = File::open(filename)?;
|
|
Ok(io::BufReader::new(file).lines())
|
|
}
|
|
|
|
fn parse_input(file_path: &str) -> io::Result<(SeedNumbers, Categories)> {
|
|
let mut seeds_numbers = SeedNumbers::new();
|
|
let mut categories = Categories::new();
|
|
let mut current_category = Category::new();
|
|
let mut is_seed_line = true;
|
|
|
|
for line in read_lines(file_path)? {
|
|
let line = line?;
|
|
if line.is_empty() {
|
|
if !current_category.is_empty() {
|
|
categories.push(current_category);
|
|
current_category = Category::new();
|
|
}
|
|
is_seed_line = false;
|
|
continue;
|
|
}
|
|
|
|
if is_seed_line {
|
|
let seeds_ranges: Vec<i32> = line
|
|
.split_whitespace()
|
|
.skip(1)
|
|
.filter_map(|s| i32::from_str(s).ok())
|
|
.collect();
|
|
|
|
for seeds in seeds_ranges.chunks(2) {
|
|
if seeds.len() == 2 {
|
|
seeds_numbers.extend((seeds[0]..seeds[0] + seeds[1]).map(|n| (n, n + 1)));
|
|
}
|
|
}
|
|
} else {
|
|
let numbers: Vec<i32> = line
|
|
.split_whitespace()
|
|
.filter_map(|s| i32::from_str(s).ok())
|
|
.collect();
|
|
if numbers.len() == 3 {
|
|
current_category.push((numbers[0], numbers[1], numbers[2]));
|
|
}
|
|
}
|
|
}
|
|
if !current_category.is_empty() {
|
|
categories.push(current_category);
|
|
}
|
|
|
|
Ok((seeds_numbers, categories))
|
|
}
|
|
|
|
fn process_categories(mut seeds_numbers: SeedNumbers, categories: Categories) -> SeedNumbers {
|
|
for category in categories {
|
|
let mut sources = SeedNumbers::new();
|
|
while let Some((start, end)) = seeds_numbers.pop() {
|
|
let mut is_mapped = false;
|
|
for &(destination, source, length) in &category {
|
|
let overlap_start = std::cmp::max(start, source);
|
|
let overlap_end = std::cmp::min(end, source + length);
|
|
if overlap_start < overlap_end {
|
|
sources.push((
|
|
overlap_start - source + destination,
|
|
overlap_end - source + destination,
|
|
));
|
|
if overlap_start > start {
|
|
seeds_numbers.push((start, overlap_start));
|
|
}
|
|
if end > overlap_end {
|
|
seeds_numbers.push((overlap_end, end));
|
|
}
|
|
is_mapped = true;
|
|
break;
|
|
}
|
|
}
|
|
if !is_mapped {
|
|
sources.push((start, end));
|
|
}
|
|
}
|
|
seeds_numbers = sources;
|
|
}
|
|
seeds_numbers
|
|
}
|
|
|
|
fn find_lowest_location(file_path: &str) -> io::Result<i32> {
|
|
let (seeds_numbers, categories) = parse_input(file_path)?;
|
|
let processed_seeds = process_categories(seeds_numbers, categories);
|
|
let lowest_location = processed_seeds
|
|
.iter()
|
|
.map(|&(start, _)| start)
|
|
.min()
|
|
.unwrap();
|
|
Ok(lowest_location)
|
|
}
|
|
|
|
fn main() -> io::Result<()> {
|
|
// Test
|
|
let test_result = find_lowest_location("../test.txt")?;
|
|
assert_eq!(
|
|
test_result, 46,
|
|
"Test failed. Expected 46, got {}",
|
|
test_result
|
|
);
|
|
println!("Test passed.");
|
|
|
|
// Process actual input
|
|
let result = find_lowest_location("../input.txt")?;
|
|
println!("Total result from input.txt: {}", result);
|
|
|
|
Ok(())
|
|
}
|