Vladimir Nabokov
Vladimir Nabokov

Reputation: 1907

Java 8 stream, how to "break" in reduce or in collect without throwing runtime exception?

This question have been asked in a context of forEach.

Comment(after the answer was accepted): I accepted the answer of @nullpointer, but it is the right one only in the context of my code example, not in the general question about break-ability of reduce..

The question:

But is there a way in reduce or in collect to "break" prematurely, without going through all the stream elements? (That means I need accumulate the state while iterating, so I use reduce or collect).

In short: I need iterate all elements of the stream (elements are integers and ordered from small to big), but look into 2 neighbor elements and compare them, if difference between them is greater than 1, I need "break" and stop "accumulate the state" and I need return the last passed element.

Variant to throw a RuntimeException and variant to pass external state - bad for me.

Code example with comments :

public class Solution {

public int solution(int[] A) {

    Supplier<int[]> supplier = new Supplier<int[]>() {
        @Override
        public int[] get() {
            //the array describes the accumulated state:
            //first element in the array , if set > 0, means  - the result is achieved, we can stop iterate over the rest elements
            //second element in the array will represent the "previous element" while iterating the stream
            return new int[]{0, 0};
        }
    };

    //the array in accumulator describes the accumulated state:
    //first element in the array , if set > 0, means  - the result is achieved, we can stop iterate over the rest elements
    //second element in the array will represent the "previous element" while iterating the stream
    ObjIntConsumer<int[]> accumulator = new ObjIntConsumer<int[]>() {
        @Override
        public void accept(int[] sett, int value) {
            if (sett[0] > 0) {
                ;//do nothing, result is set
            } else {
                if (sett[1] > 0) {//previous element exists
                    if (sett[1] + 1 < value) {
                        sett[0] = sett[1] + 1;
                    } else {
                        sett[1] = value;
                    }
                } else {
                    sett[1] = value;
                }
            }
        }
    };

    BiConsumer<int[], int[]> combiner = new BiConsumer<int[], int[]>() {
        @Override
        public void accept(int[] sett1, int[] sett2) {
            System.out.println("Combiner is not used, we are in sequence");
        }
    };

    int result[] = Arrays.stream(A).sorted().filter(value -> value > 0).collect(supplier, accumulator, combiner);
    return result[0];
}


/**
 * We have an input array
 * We need order it, filter out all elements that <=0 (to have only positive)
 * We need find a first minimal integer that does not exist in the array
 * In this example it is 5
 * Because 4,6,16,32,67 positive integers array is having 5 like a minimum that not in the array (between 4 and 6)
 *
 * @param args
 */
public static void main(String[] args) {
    int[] a = new int[]{-2, 4, 6, 16, -7, 0, 0, 0, 32, 67};
    Solution s = new Solution();
    System.out.println("The value is " + s.solution(a));
}

}

Upvotes: 5

Views: 4626

Answers (2)

Naman
Naman

Reputation: 32028

Given an array as input, seems to me you're looking for something like this :

int stateStream(int[] arr) {
    return IntStream.range(0, arr.length - 1)
            .filter(i -> arr[i + 1] - arr[i] > 1) // your condition
            .mapToObj(i -> arr[i])
            .findFirst() // first such occurrence
            .map(i -> i + 1) // to add 1 to the point where the cehck actually failed
            .orElse(0); // some default value
}

or from the scratch, while you convert it to a sorted and filtered value list as :

int stateStream(int[] arr) {
    List<Integer> list = Arrays.stream(arr)
            .boxed().sorted()
            .filter(value -> value > 0)
            .collect(Collectors.toList());
    return IntStream.range(0, list.size() - 1)
            .filter(i -> list.get(i + 1) - list.get(i) > 1)
            .mapToObj(list::get)
            .findFirst()
            .map(i -> i + 1)
            .orElse(0);
}

Upvotes: 3

Danila Zharenkov
Danila Zharenkov

Reputation: 1863

The is no ways in streams API to break. You can throw an Exception, but it's really not a good idea. But you are right - you can use reduce to find last "successed" element in collection

The list of ints:

List<Integer> integers = Arrays.asList(1,2,3,4,5,6,7,8,9,10,12,13);

Lets find the value of i-th element, where element[i+1]-element[i] > 1 :

int result = integers.stream().reduce((i1,i2) -> (i2-i1) > 1 ? i1 : i2).get();

For this case result will be equals to 10. And then you can just get the sublist of your common list;

integers.subList(0,integers.indexOf(result)+1).forEach(s-> System.out.println(s));

For cases of valid collection (when there are no elements with difference > 1) the result will be equals to the value of last element and sublist will be equals to list. So you can add some check to avoid .subList when it's not neccessary.

Example for reduce:

{1,2,3,5}

Step1:

i1 = 1; i2 = 2; -> reduce(), difference =1, so we reduce this pair to i2 (2)  -> new collection is{2,3,5}

Step2

i1 = 2; i2 = 3; -> reduce(), difference =1, so we reduce this pair to i2 (3)  -> new collection is{3,5}

Step3

i1 = 3; i2 = 5; -> reduce(), difference >1, so we reduce this pair to i1 (3)  -> new collection is {3} and it transforms to Optional<Integer>

Upvotes: 2

Related Questions