Today’s puzzle is a simple optimization problem. The objective is to minimize a cost function.
The function to minimize can be written as :
where has the following properties :
- is decreasing on and increasing on
We understand easily that the minimum the function is in the interval , where is the minimum of the numbers and is the maximum.
Demonstration :
Part 1
In this part, the cost function is the absolute value function
pub fn solve_part1(input: &Vec<String>) -> u64 {
let positions = input.iter().map(|x| x.parse().unwrap()).collect::<Vec<i64>>();
let min_x = positions.iter().cloned().min().unwrap();
let max_x = positions.iter().cloned().max().unwrap();
let mut fuel_costs = HashMap::<i64, u64>::new();
let mut x = min_x;
while x <= max_x {
fuel_costs.insert(x,
positions.iter()
.map(|x0| (x - *x0).abs() as u64).sum::<u64>());
x += 1;
}
*fuel_costs.values().min().unwrap()
}
Part 2
In the second part, the function is slightly more complex. It is defined as :
For example, if and ,
It is possible to simplify the calculation of this function, using the technique discovered by Carl Friedrich Gauss at the age of 8. We know that :
Therefore, the function f_{i} becomes :
and its calculation is done in constant time. With the naive formula, it would be done in linear time.
The part 2 main function is only slightly modified, in the while block.
pub fn solve_part2(input: &Vec<String>) -> u64 {
let positions = input.iter().map(|x| x.parse().unwrap()).collect::<Vec<i64>>();
let min_x = positions.iter().cloned().min().unwrap();
let max_x = positions.iter().cloned().max().unwrap();
let mut fuel_costs = HashMap::<i64, u64>::new();
let mut x = min_x;
while x <= max_x {
fuel_costs.insert(x,
positions.iter()
.map(|x0| ((x - *x0).abs() * ((x - *x0).abs()+1)/2) as u64)
.sum::<u64>());
x += 1;
}
*fuel_costs.values().min().unwrap()
}
Performances
Today, I discovered the --release option of the cargo run (or cargo build) command. This is the normal option used to build and run a Rust program in production, and this the one that should be used to measure program performances.
With the --release option, the part 1 runs in 700 µs, and the part 2 in 1800 µs. Without this option, both parts run in 60000 µs.