I want to compute the maximum value of a two-dimensional vector of f64
s in Rust.
What is the easiest and most performant way to do accomplish this?
My vector is declared as follows:
let (width,height) = (1920,1080);
let mut flow = vec![vec![0.0; height as usize]; width as usize];
My intuitive solution based on this answer would be the following: (Playground Link)
let max: &f64 = flow.iter()
.map(|f| f
.iter()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr)))
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr));
However, that code does not compile and only works by converting the iterator of the inner vector into a reference: (Playground Link)
let max: &f64 = &(flow.iter()
.map(|f| f
.iter()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr))))
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(curr));
Why do I need to convert the inner iterator into a reference, and is there a better way to find the maximum of a two-dimensional f64 vector?
CodePudding user response:
The right way to write your loops would be something like this:
let max: f64 = flow.iter()
.map(|f| f
.iter()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr)))
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(curr));
To understand it, it helps to annotate the type of each intermediate value and variables:
let max: f64 = flow // Vec<Vec<f64>>
.iter() // Iterator<Item=&Vec<f64>>
.map(|f| f // f: &Vec<f64>
.iter() // Iterator<Item=&f64>
.fold(
f64::NEG_INFINITY, // f64
|prev, curr| // prev: f64, curr: &f64 -> f64
prev.max(*curr)
) // return of fold(): f64
) // return of map(): Iterator<Item=f64>
.fold(
f64::NEG_INFINITY, // f64
|prev, curr| // prev: f64, curr: f64 -> f64
prev.max(curr)
) // return of fold(): f64
;
Note the signature of Iterator::fold
:
fn fold<B, F>(self, init: B, f: F) -> B where
F: FnMut(B, Self::Item) -> B,
This means that it returns the type of its first argument, in your case the first argument is always f64::NEG_INFINITY
.
And in the closure, the first argument is of that same type, while the second is the type of the Item
from the iterator where fold
is used. In the first fold
that Item
is &f64
but in the second one it is f64
.
That is why in the first closure curr: &f64
while in the second one curr: f64
, that I think was the most confusing point.
That said, I would write instead:
let max = flow
.iter()
.flatten()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr));
Iterator::flatten
is a function that applied to an iterator that returns something that implements IntoIterator<Item=T>
returns a value that implements Iterator<Item=T>
and that iterates over the whole two-dimensional thing. Exactly what you need, I think.
If you want to get rid of the reference in curr
, useful if your code in fold
is more complex, you can also add copied()
:
let max = flow
.iter()
.flatten()
.copied()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(curr));
Iterator::copied
just converts an Iterator<Item=&T>
into an Iterator<Item=T>
, as long as T: Copy
.