Write reduce function to reduce a vector to a value

fn reducer(v: &Vec<i32>, reduce_fn: fn(i32, i32) -> i32) -> I32 {
    let result = v.iter().reduce(|a, c| &reduce_fn(*a, *c));
    match result {
        Some(&v) => v,
            None => 0
    }
}

fn add(x: i32, y: i32) -> i32 {
    x+y
}

fn main() {
    let v:Vec<i32> = (0..10).collect();
    println!("{v:?} {}", reducer(&v, add));
}

I am not sure what I can do.

Here are the things that I want to do with this code:

  1. Work on the reference of a vector. Don't want to clone.
  2. Pass any function of the type (i32, i32) -> i32.
  3. Reduce the vector to an i32 value based on the function passed.
  1. You usually don't want to work on the reference of a vector, since borrowed slice, that is, &[u32], is strictly more powerful (it allows essentially everything that &Vec<u32> does).
  2. For that simple case, you can just insert .copied(), drop all the corresponding referencing/dereferencing, and everything will work - playground.
  3. If you were working with non-Copy types (and either don't want to use .cloned(), or the types are non-Clone either), you'd have to use fold instead of reduce and provide the default owned value explicitly.
4 Likes

you don't need vector specifically, reduce() is defined in Iterator. this will work for any iterators that yields i32:

fn reducer(v: impl Iterator<Item = i32>, reduce_fn: fn(i32, i32) -> i32) -> I32 {
    v.reduce(reduce_fn).unwrap_or(0)
}

then you can use the Fn trait bound, instead of a regular function pointer:

fn reducer(v: impl Iterator<Item = i32>, reduce_fn: impl Fn(i32, i32) -> i32) -> I32 {
    v.reduce(reduce_fn).unwrap_or(0)
}

this will accept Iterator<Item = i32> directly, but slice::iter() will give you an Iterator<Item = &i32>, you can use Iterator::cloned() to get an Iterator<Item = i32>, like this:

let v = &[1, 2, 3, 4];
let r = reducer(v.iter().cloned(), add);

to accept both Iterator<Item = i32> and Iterator<Item = &i32>, you'll need a trait that both &i32 and i32 implement. you can define a marker trait yourself, or there's a trait in the standard library with reflexivity, i.e. std::borrow::Borrow<i32>, (although it feels kind of wrong to abuse Borrow here, due to the semantical implications).

also, you can use IntoIterator in place of Iterator so the function can also accept a slice or array directly (no need to call iter() on it)

so the final form is something like this:

// marker trait for i32 and &i32
trait ToI32 { fn to_i32(&self) -> i32; }
impl ToI32 for i32 { fn to_i32(self) -> i32 { self } }
impl<'a> ToI32 for &'a i32 { fn to_i32(self) -> i32 { *self } }

// generic function
fn reducer<I, F>(values: I, f: F) -> i32
where
    I: IntoIterator,
    I::Iterm: ToI32,
    F: Fn(i32, i32) -> i32
{
    values.into_iter().map(ToI32::to_i32).reduce(f).unwrap_or(0)
}

// example usage
fn main() {
    let values = [1, 2, 3, 4];
    // slice
    reducer(&values[..], add);
    // array
    reducer(values, add);

    let values = vec![1, 2, 3, 4];
    // Iterator<Item = &i32>
    reducer(values.iter(), add);
    // Iterator<Item = i32>
    reducer(values.into_iter(), add);
}
2 Likes