advent-of-code/aoc2021/src/day07.rs

169 lines
3.7 KiB
Rust

use std::fmt::Write;
use anyhow::{Context, Result};
use rand::Rng;
const INPUT: &str = include_str!("../input/day07.txt");
pub fn run() -> Result<String> {
let mut res = String::with_capacity(128);
writeln!(res, "part 1: {}", part1(INPUT)?)?;
writeln!(res, "part 2: {}", part2(INPUT)?)?;
Ok(res)
}
fn part1(input: &str) -> Result<u64> {
let mut horizontal_positions = input
.trim()
.split(',')
.map(|n| n.parse::<u64>().context("couldn't parse position"))
.collect::<Result<Vec<_>>>()?;
let median_rank = horizontal_positions.len() / 2;
let median = selection(&mut horizontal_positions, median_rank);
Ok(horizontal_positions
.iter()
// TODO: use abs_diff when stabilized
.map(|n| abs_diff(*n, median))
.sum())
}
fn selection<T>(data: &mut [T], i: usize) -> T
where
T: Copy + Ord,
{
if data.len() == 1 {
return data[0];
}
let mid = random_partition(data);
if i < mid {
selection(&mut data[..mid], i)
} else {
selection(&mut data[mid..], i - mid)
}
}
fn random_partition<T>(data: &mut [T]) -> usize
where
T: Copy + Ord,
{
let pivot_index = rand::thread_rng().gen_range(0..data.len());
let pivot = data[pivot_index];
let mut i = 0;
let mut j = data.len() - 1;
loop {
while data[i] < pivot {
i += 1;
}
while data[j] > pivot {
j -= 1;
}
if i >= j {
return usize::max(i, 1);
}
data.swap(i, j);
i += 1;
j -= 1;
}
}
fn part2(input: &str) -> Result<u64> {
let horizontal_positions = input
.trim()
.split(',')
.map(|n| n.parse::<u64>().context("couldn't parse position"))
.collect::<Result<Vec<_>>>()?;
let min = *horizontal_positions.iter().min().unwrap();
let max = *horizontal_positions.iter().max().unwrap();
let optimal_distance = minimize_binary_search(&horizontal_positions, min, max, part2_distance);
let minimum_fuel = horizontal_positions
.iter()
.map(|n| part2_distance(*n, optimal_distance))
.sum::<u64>();
Ok(minimum_fuel)
}
fn minimize_binary_search<F>(positions: &[u64], min: u64, max: u64, distance: F) -> u64
where
F: Fn(u64, u64) -> u64,
{
if min == max {
return min;
}
let mid1 = min + (max - min) / 2;
let mid2 = mid1 + 1;
let (cost1, cost2) = positions
.iter()
.map(|n| (distance(*n, mid1), distance(*n, mid2)))
.reduce(|(sum1, sum2), (x1, x2)| (sum1 + x1, sum2 + x2))
.unwrap();
if cost1 < cost2 {
minimize_binary_search(positions, min, mid1, distance)
} else {
minimize_binary_search(positions, mid2, max, distance)
}
}
fn abs_diff(a: u64, b: u64) -> u64 {
a.max(b) - a.min(b)
}
fn part2_distance(a: u64, b: u64) -> u64 {
let distance = abs_diff(a, b);
distance * (distance + 1) / 2
}
#[cfg(test)]
mod tests {
use super::*;
const PROVIDED: &str = include_str!("../input/day07_provided.txt");
#[test]
fn part1_provided() {
assert_eq!(part1(PROVIDED).unwrap(), 37);
}
#[test]
fn part1_real() {
assert_eq!(part1(INPUT).unwrap(), 340056);
}
#[test]
fn part2_provided() {
assert_eq!(part2(PROVIDED).unwrap(), 168);
}
#[test]
fn part2_real() {
assert_eq!(part2(INPUT).unwrap(), 96592275);
}
#[test]
fn test_selection() {
for _ in 0..4200 {
for i in 0..=9 {
let mut data = vec![9, 2, 7, 3, 5, 4, 6, 1, 8, 0];
let res = selection(&mut data, i);
assert_eq!(res, i);
}
}
}
}