简体   繁体   中英

Stateful filter for ordered stream

I have a problem and I wonder if there is a solution using Streams.

Imagine you have an ordered stream of Objects; let's assume a stream of Integers.

 Stream<Integer> stream = Stream.of(2,20,18,17,4,11,13,6,3,19,4,10,13....)

Now I want to filter all values where the difference of a value and the previous number before this value is greater than n .

stream.filter(magicalVoodoo(5))
// 2, 20, 4, 11, 3, 19, 4, 10 ...

I there any possibility to do this?

Yes, this is possible, but you will need a stateful predicate that keeps track of the previous value for doing the comparison. This does mean it can only be used for sequential streams: with parallel streams you'd run into race conditions.

Luckily, most streams default to sequential, but if you need to do this on streams from an unknown source, you may want to check using isParallel() and either throw an exception, or convert it to a sequential stream using sequential() .

An example:

public class DistanceFilter implements IntPredicate {

    private final int distance;
    private int previousValue;

    public DistanceFilter(int distance) {
        this(distance, 0);
    }

    public DistanceFilter(int distance, int startValue) {
        this.distance = distance;
        this.previousValue = startValue;
    }

    @Override
    public boolean test(int value) {
        if (Math.abs(previousValue - value) > distance) {
            previousValue = value;
            return true;
        }
        return false;
    }

    // Just for simple demonstration
    public static void main(String[] args) {
        int[] ints = IntStream.of(2, 20, 18, 17, 4, 11, 13, 6, 3, 19, 4, 10, 13)
                .filter(new DistanceFilter(5))
                .toArray();

        System.out.println(Arrays.toString(ints));
    }
}

I used IntStream here, because it is a better type for this, but the concept would be similar for Stream<Integer> (or other object types).

Streams are not designed for this kind of task. I would use a different way to accomplish this, which doesn't use streams. But, if you really must use streams, the solution has to circumvent certain restrictions due to the design of streams and lambdas, and therefore looks quite hacky:

int[] previous = new int[1];
previous[0] = firstElement;
... = stream.filter(n -> {
        boolean isAllowed = (abs(n - previous[0]) > 5);
        if (isAllowed)
            previous[0] = n;
        return isAllowed;})

Notice that the variable previous is a one-element array. That's a hack due to the fact that the lambda is not allowed to modify variables (it can modify an element of an array, but not the array itself).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM