How to compare the value of next element with the current element in an iterator without a loop and for_each?

Viewed 53

I have a vector like [1, 2, 4, 3], I want to remove 3 because 3 is smaller than 4. I want to use the iterator to solve this problem, and do not use the loop or for_each. The first step I think need to do is vec.into_iter, but I don't know what to do next.

3 Answers

To reformulate, you want to remove any element smaller than the previous element.

Let's write a function to do so. As you want to work exclusively with iterators, therefore in a functional style, we are going to assume the input vector is immutable, so the function should take a slice as input, and return a new Vec:

fn remove_smaller<T: Ord + Copy>(v: &[T]) -> Vec<T> {
    v.iter()
        .rev()
        .collect::<Vec<_>>()
        .windows(2)
        .filter(|a| a[0] > a[1])
        .map(|a| *a[0])
        .chain([v[0]])
        .rev()
        .collect()
}

Let's explain what this function is doing, using vec![1, 2, 4, 3] as sample input.

We first reverse the order of the vector so we can operate on windows looking at the previous value, and collect it into a new vector (needed as windows is implemented for slices only).

&[3, 4, 2, 1]

windows(2) returns an iterator that will yield overlapping pairs of elements of the slice, except the last element, which has no next:

&[3, 4], &[4, 2], &[2, 1]

We then filter with filter(|a| a[0] > a[1]) meaning we only keep entries which are ordered (hence why type of input needs to be Ord):

&[4, 2], &[2, 1]

We then map with map(|a| *a[0]) in order to keep each value, which needs T to be Copy:

4, 2

Now, since we are missing the first element of the input array, we need to add it again, using .chain([v[0]]) giving us:

4, 2, 1

We then reverse the iterator to obtain the output array in correct order:

1, 2, 4

See it in action in the playground.

This is not a very efficient method to achieve the result, as it needs to allocate twice as much memory as the input.

You can use the zip and skip functionality to put together two elements of an array.

Following the footsteps of @sirdarius, Here is how your function can be:

fn remove_smaller<T: Ord + Copy>(v: &[T]) -> Vec<T> {
    let mut res = vec![v[0]];
    res.extend(
        v.iter()
            .zip(v.iter().skip(1))
            .filter(|(a, b)| a < b)
            .map(|(_, b)| *b),
    );
    res
}

Walk through:

We fist create our result vector and push the first element in it since it is always in the answer vector.

Then we extend our result vector by another iterator which would perform the following:

create a tuple for each element of the array with indices of the same array but one index ahead (v.iter().skip(1)).

we then filter out pairs which meet our ordering and finally, we map the pair to a single value.

There is an iterator only way to do what you wanted in O(1) space.

fn non_decreasing(v: Vec<i32>) -> Vec<i32> {
    if v.is_empty() {
        return v;
    }
    let first = v[0];
    once(first)
        .chain(
            v.into_iter()
                .skip(1)
                .scan(first, |last_max, cur_elem| {
                    if cur_elem < *last_max {
                        Some(None)
                    } else {
                        *last_max = cur_elem;
                        Some(Some(cur_elem))
                    }
                })
                .flatten(),
        )
        .collect()
}

This function will not use any extra space (even for the output, on newer rustc versions). It will return a vector that's non-decreasing. That is, each element in the result vector will be >= the previous one.

If you wanted to compare the elements only to the previous element and not the previous largest, then just add the *last_max = cur_elem line to the if branch as well.

Related