339 lines
11 KiB
Rust
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);
|
|
}
|
|
}
|