Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I replicate Haskell's `scanl (+) 0 xs` in Rust?

Tags:

haskell

rust

If I have an list of numbers [1, 2, 3, 4, 5] and I wanted to generate a cumulative sum list, in Haskell I would do the following:

> let xs = [1, 2, 3, 4, 5]

> scanl (+) 0 xs
[0,1,3,6,10,15]

Trying to get this same behaviour seems unnecessarily troublesome in Rust.

let xs = [1, 2, 3, 4, 5];

let vs = vec![0]
    .into_iter()
    .chain(xs.iter().scan(0, |acc, x| {
        *acc += x;
        Some(*acc)
    }))
    .collect::<Vec<_>>();

The awkward scan behaviour of having to mutate the accumulator can be explained by a lack of GC. But, scan also does not include the initial accumulator value, necessitating the need to manually prepend a 0 at the front. This itself was troublesome, as I needed to prepend it with chain and [0].iter() didn't work, nor did [0].into_iter() and vec![0].iter(). It needed vec![0].into_iter().

I feel like I must be doing something wrong here. But, what? Is there a better way to generate a cumulative sum? Is it back to a for loop?

like image 514
Listerone Avatar asked Jun 14 '19 05:06

Listerone


3 Answers

Edit :

Despite the old version of this answer mimics the behavior of scanl's intermediate form, the execution wasn't lazy. Updated the generic implementation from my old answer with @French Boiethios's answer.

This is the implementation :

fn scanl<'u, T, F>(op: F, initial: T, list: &'u [T]) -> impl Iterator<Item = T> + 'u
where
    F: Fn(&T, &T) -> T + 'u,
{
    let mut iter = list.iter();
    std::iter::successors(Some(initial), move |acc| iter.next().map(|n| op(n, acc)))
}
//scanl(|x, y| x + y, 0, &[1, 2, 3, 4, 5]).collect::<Vec<_>>()

Playground


It can be easily implemented by a fold

For an Add operation:

let result = xs.iter().fold(vec![0], |mut acc, val| {
    acc.push(val + acc.last().unwrap());
    acc
});

Playground


Here is the generic version :

fn scanl<T, F>(op: F, initial: T, list: &[T]) -> Vec<T>
where
    F: Fn(&T, &T) -> T,
{
    let mut acc = Vec::with_capacity(list.len());
    acc.push(initial);

    list.iter().fold(acc, |mut acc, val| {
        acc.push(op(val, acc.last().unwrap()));
        acc
    })
}
//scanl(|x, y| x + y, 0, &[1, 2, 3, 4, 5])

Playground

like image 116
Ömer Erden Avatar answered Nov 05 '22 20:11

Ömer Erden


I would do that with successors:

fn main() {
    let mut xs = vec![1, 2, 3, 4, 5].into_iter();
    let vs = std::iter::successors(Some(0), |acc| xs.next().map(|n| n + *acc));

    assert_eq!(vs.collect::<Vec<_>>(), [0, 1, 3, 6, 10, 15]);
}
like image 26
Boiethios Avatar answered Nov 05 '22 20:11

Boiethios


The awkward scan behaviour of having to mutate the accumulator can be explained by a lack of GC.

There is nothing preventing Rust from doing what you ask.

Example of possible implementation:

pub struct Mapscan<I, A, F> {
    accu: Option<A>,
    iter: I,
    f: F,
}

impl<I, A, F> Mapscan<I, A, F> {
    pub fn new(iter: I, accu: Option<A>, f: F) -> Self {
        Self { iter, accu, f }
    }
}

impl<I, A, F> Iterator for Mapscan<I, A, F>
where
    I: Iterator,
    F: FnMut(&A, I::Item) -> Option<A>,
{
    type Item = A;

    fn next(&mut self) -> Option<Self::Item> {
        self.accu.take().map(|accu| {
            self.accu = self.iter.next().and_then(|item| (self.f)(&accu, item));
            accu
        })
    }
}

trait IterPlus: Iterator {
    fn map_scan<A, F>(self, accu: Option<A>, f: F) -> Mapscan<Self, A, F>
    where
        Self: Sized,
        F: FnMut(&A, Self::Item) -> Option<A>,
    {
        Mapscan::new(self, accu, f)
    }
}

impl<T: ?Sized> IterPlus for T where T: Iterator {}

fn main() {
    let xs = [1, 2, 3, 4, 5];

    let vs = xs
        .iter()
        .map_scan(Some(0), |acc, x| Some(acc + x));

    assert_eq!(vs.collect::<Vec<_>>(), [0, 1, 3, 6, 10, 15]);
}
like image 33
Stargateur Avatar answered Nov 05 '22 19:11

Stargateur