Java 8流(Stream),如何在reduce或collect中“中断”而不抛出运行时异常?

5

这个问题是针对forEach的上下文提出的。

评论(在答案被接受后):我接受了 @nullpointer 的答案,但它只在我的代码示例的情况下正确,而不适用于关于 reduce 可否“中断”的一般性问题。

问题:

但是,在reducecollect中是否有一种方式可以“中断”操作,而不必遍历所有流元素?(这意味着需要在迭代时累积状态,因此我使用reducecollect)。

简而言之:我需要遍历流的所有元素(元素为整数且按顺序从小到大),但要查看2个相邻元素并比较它们。如果它们之间的差大于1,则需要“中断”并停止“累积状态”,并且需要返回最后一个传递的元素。

抛出RuntimeException的变体和传递外部状态的变体-不适合我。

带有注释的代码示例:

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));
}

}


5
你能详细说明一下,“累积状态”是什么意思吗?并且我需要返回最后传递的元素。 - Naman
4
并非所有的循环都最适合用流来替代。如果您的行为像这样,使用循环可能会更清晰(即使有一些方法可以通过流来实现您想要的效果)。 - Peter Lawrey
添加了代码示例 - Vladimir Nabokov
2个回答

3
给定一个数组作为输入,我认为你正在寻找类似于这样的东西:
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
}

或者你可以从头开始,将其转换为已排序和过滤的值列表,如下所示:
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);
}

太好了,我学到了很多!但我不能接受那个作为答案,因为过滤器会遍历整个数组...(在第一个例子中),在第二个例子中是2-d过滤器,我不喜欢。我的意思是,如果已经创建了正数数组,我更喜欢一个答案,在这个答案中我们不需要传递整个数组,只需转到满足条件的第一个元素即可。 - Vladimir Nabokov
1
@VladimirNabokov 不,它不会遍历完整个列表,在那里使用findFirst()有所帮助(如果这是你的意思的话)...第二个实现是你可以直接替换在你的main方法中的实现。 - Naman
1
我必须说,在代码示例的上下文中,这是正确的答案。它解决了问题,但在最初的问题“reduce是否可中断?”的上下文中,它不是答案,答案是“它是不可中断的”(可能)。 - Vladimir Nabokov

2

在流API中没有break的方法。你可以抛出异常,但这真的不是一个好主意。但是你是对的——你可以使用reduce来查找集合中最后一个“成功”的元素。

整数列表:

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

让我们找到第i个元素的值,其中element[i+1]-element[i] > 1

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

对于这个案例,结果将等于10。然后你可以只获取你的常规列表的子列表;
integers.subList(0,integers.indexOf(result)+1).forEach(s-> System.out.println(s));

对于有效集合(即没有元素差大于1的情况),result将等于最后一个元素值,子列表将等于列表。因此,您可以添加一些检查来避免在不必要时使用.subList

reduce示例:

{1,2,3,5}

步骤1:

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}

第三步

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>

1
i1和i2是lambda表达式的占位符。它们只是一对最近的元素。在答案中添加了一些示例。 - Danila Zharenkov
1
是的,所有元素都会被遍历,因为如我之前所写,在Streams中没有中断条件。最简单的答案是它为什么将i1和i2解释为相邻元素:当您遍历集合时,按顺序一个一个地取元素,对吧?这里是相同的情况,只是多了一步 - 您逐对取出每一对元素。所以,如果您的集合已排序,则它们将是差异最小的相邻元素。抱歉,如果我的表达不够清晰,我的解释能力不太强 :) - Danila Zharenkov
API是Java 8流式API。文檔-https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html。 - Danila Zharenkov
Danila,i1和i2是占位符,但它们是移动占位符,i1始终包含减少的结果,而i2是下一个元素。我只是无法理解,如何从一开始就将i1初始化为0...在开始时,没有默认值用于减少... - Vladimir Nabokov
我会接受你的回答,“在流API中没有打破的方法”,只是以后...也许有些天才会提供一个解决方案 :) ..非常怀疑.. - Vladimir Nabokov
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接