Home > Back-end >  max of a two-dimensional f64 vector in Rust
max of a two-dimensional f64 vector in Rust

Time:07-09

I want to compute the maximum value of a two-dimensional vector of f64s 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.

  • Related