Files
contests/advent_of_code/2023/5/src/main.rs

339 lines
11 KiB
Rust

use nom::{
bytes::complete::tag,
character::complete::{i64, multispace0, multispace1},
multi::separated_list1,
sequence::{delimited, tuple},
IResult, Parser,
};
/// This function assumes your input vector is sorted.
/// It might break if this is not the case.
/// We don't check if this is actually true, because that would take O(n) time,
/// while we aim for this function to only take up O(log n) time.
fn binary_search<'a, Item: Ord + Eq>(
value_to_find: &Item,
vector_to_find_it_in: &'a Vec<Item>,
) -> Option<&'a Item> {
if vector_to_find_it_in.is_empty() {
None
} else {
let mut index = vector_to_find_it_in.len() / 2;
let mut lower = 0;
let mut upper = vector_to_find_it_in.len() - 1;
while upper != lower {
if vector_to_find_it_in[index] <= *value_to_find {
upper = index;
} else {
lower = index;
}
index = (upper - lower) / 2;
}
if vector_to_find_it_in[index] == *value_to_find {
Some(&vector_to_find_it_in[index])
} else {
None
}
}
}
fn give_map_parser<'a: 'b, 'b: 'a>(
map_name: &'a str,
) -> impl FnMut(&'a str) -> IResult<&'b str, Vec<(i64, i64, i64)>> {
delimited(
tag(map_name),
separated_list1(
multispace1,
tuple((i64, multispace1, i64, multispace1, i64)).map(|(a, _, b, _, c)| (a, b, c)),
),
multispace0,
)
}
fn parse_input(
input: &str,
) -> IResult<
&str,
(
Vec<i64>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
),
> {
let (input, result) = tuple((
delimited(tag("seeds: "), separated_list1(tag(" "), i64), multispace1),
give_map_parser("seed-to-soil map:\n"),
give_map_parser("soil-to-fertilizer map:\n"),
give_map_parser("fertilizer-to-water map:\n"),
give_map_parser("water-to-light map:\n"),
give_map_parser("light-to-temperature map:\n"),
give_map_parser("temperature-to-humidity map:\n"),
give_map_parser("humidity-to-location map:\n"),
))(input)?;
Ok((input, result))
}
fn destination_to_source_mapping(source: i64, mappings: &Vec<(i64, i64, i64)>) -> i64 {
mappings
.iter()
.filter(|&(_destination_start, source_start, range)| {
source >= *source_start && source < *source_start + *range
})
.next()
.map(|(destination_start, source_start, _range)| {
destination_start + (source - source_start)
})
.unwrap_or(source)
}
fn solve_1(
almanac: &(
Vec<i64>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
),
) -> i64 {
almanac
.0
.iter()
.map(|&seed| {
let soil = destination_to_source_mapping(seed, &almanac.1);
let fertilizer = destination_to_source_mapping(soil, &almanac.2);
let water = destination_to_source_mapping(fertilizer, &almanac.3);
let light = destination_to_source_mapping(water, &almanac.4);
let temperature = destination_to_source_mapping(light, &almanac.5);
let humidity = destination_to_source_mapping(temperature, &almanac.6);
destination_to_source_mapping(humidity, &almanac.7)
})
.min()
.unwrap()
}
fn search_range(
input_range: (i64, i64),
mappings: &Vec<(i64, i64, i64)>,
) -> Option<(i64, i64, i64)> {
if mappings.is_empty() {
None
} else {
let mut index = mappings.len() / 2;
let mut lower = 0;
let mut upper = mappings.len() - 1;
while upper != lower {
if mappings[index].1 <= input_range.0
&& mappings[index].1 + mappings[index].2 > input_range.0
{
return Some(mappings[index]);
} else if mappings[index].1 < input_range.0 {
lower = index;
} else {
upper = index - 1;
}
index = (upper + lower).div_ceil(2);
}
if index >= mappings.len() - 1 && mappings[index].1 + mappings[index].2 < input_range.1 {
None
} else if mappings[index].1 == input_range.0 {
mappings.get(index + 1).copied()
} else {
Some(mappings[index])
}
}
}
/// We will assume the mappings are sorted on the source, so we can more quickly, using binary search, find
/// the suitable ranges. This is relevant because of the large numbers involved.
fn get_suitable_range(input_range: (i64, i64), mappings: &Vec<(i64, i64, i64)>) -> Vec<(i64, i64)> {
let mut found_until = input_range.0;
let mut result = vec![];
while found_until < input_range.1 {
println!(
"{} {:?} {:?}",
found_until,
mappings,
search_range((found_until, input_range.1), mappings)
);
if let Some((destination_start, source_start, range)) =
search_range((found_until, input_range.1), mappings)
{
if source_start > found_until {
println!("ding");
result.push((found_until, std::cmp::min(source_start, input_range.1)));
found_until = std::cmp::min(source_start, input_range.1);
} else {
println!("dong");
let max_source = std::cmp::min(source_start + range, input_range.1);
result.push((
found_until + (destination_start - source_start),
destination_start + (max_source - source_start),
));
found_until = max_source;
}
} else {
println!("dang");
result.push((found_until, input_range.1));
found_until = input_range.1;
}
}
result
}
fn get_suitable_ranges(
input_ranges: &Vec<(i64, i64)>,
mappings: &Vec<(i64, i64, i64)>,
) -> Vec<(i64, i64)> {
let mut output_ranges_uncompressed = input_ranges
.iter()
.map(|&(range_start, range_length)| {
get_suitable_range((range_start, range_start + range_length), mappings)
})
.collect::<Vec<Vec<(i64, i64)>>>()
.into_iter()
.flatten()
.collect::<Vec<(i64, i64)>>();
output_ranges_uncompressed.sort_unstable();
let (mut lower, mut upper) = output_ranges_uncompressed[0];
let mut output_ranges = vec![];
for (range_low, range_up) in output_ranges_uncompressed {
if range_low <= upper {
upper = range_up;
} else {
output_ranges.push((lower, upper));
lower = range_low;
upper = range_up;
}
}
output_ranges.push((lower, upper));
output_ranges = output_ranges
.iter()
.map(|&(range_start, range_end)| (range_start, range_end - range_start))
.collect::<Vec<_>>();
output_ranges
}
fn solve_2(
almanac: &(
Vec<i64>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
Vec<(i64, i64, i64)>,
),
) -> i64 {
let mut lowest_location_numbers = vec![];
let mut seed_iter = almanac.0.iter();
let mut soil_almanac = almanac.1.iter().map(|&n| n).collect::<Vec<_>>();
soil_almanac.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
let mut fertilizer_almanac = almanac.2.iter().map(|&n| n).collect::<Vec<_>>();
fertilizer_almanac
.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
let mut water_almanac = almanac.3.iter().map(|&n| n).collect::<Vec<_>>();
water_almanac
.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
let mut light_almanac = almanac.4.iter().map(|&n| n).collect::<Vec<_>>();
light_almanac
.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
let mut temperature_almanac = almanac.5.iter().map(|&n| n).collect::<Vec<_>>();
temperature_almanac
.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
let mut humidity_almanac = almanac.6.iter().map(|&n| n).collect::<Vec<_>>();
humidity_almanac
.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
let mut location_almanac = almanac.7.iter().map(|&n| n).collect::<Vec<_>>();
location_almanac
.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2));
while let (Some(&lower_seed), Some(&range_length)) = (seed_iter.next(), seed_iter.next()) {
println!("ding");
let seed_range = vec![(lower_seed, range_length)];
let soil_ranges = get_suitable_ranges(&seed_range, &soil_almanac);
println!("soil");
let fertilizer_ranges = get_suitable_ranges(&soil_ranges, &fertilizer_almanac);
println!("fertilizer");
let water_ranges = get_suitable_ranges(&fertilizer_ranges, &water_almanac);
println!("water");
let light_ranges = get_suitable_ranges(&water_ranges, &light_almanac);
println!("light");
let temperature_ranges = get_suitable_ranges(&light_ranges, &temperature_almanac);
println!("temperature");
let humidity_ranges = get_suitable_ranges(&temperature_ranges, &humidity_almanac);
println!("humidity");
lowest_location_numbers.push(
get_suitable_ranges(&humidity_ranges, &location_almanac)
.iter()
.map(|&(range_min, _range_max)| range_min)
.min()
.unwrap(),
);
}
*lowest_location_numbers.iter().min().unwrap()
}
fn main() {
println!("Hello, this is Patrick!");
let input_text = include_str!("../input.txt");
let (_, almanac) = parse_input(input_text).unwrap();
println!(
"The lowest location number corresponding to the initial seed numbers is {}",
solve_1(&almanac)
);
println!(
"The lowest location number for any of the initial seed ranges is {}",
solve_2(&almanac)
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_1() {
let input_text = include_str!("../test_input.txt");
let (_, almanac) = parse_input(input_text).unwrap();
assert_eq!(solve_1(&almanac), 35);
}
#[test]
fn test_2() {
let input_text = include_str!("../test_input.txt");
let (_, almanac) = parse_input(input_text).unwrap();
assert_eq!(solve_2(&almanac), 46);
}
}