【问题标题】:Java 8 stream, how to "break" in reduce or in collect without throwing runtime exception?Java 8流,如何在reduce或collect中“中断”而不抛出运行时异常?
【发布时间】:2019-05-26 20:10:37
【问题描述】:

这个问题是在forEach 的上下文中提出的。

评论(在接受答案后):我接受了@nullpointer 的答案,但它仅在我的代码示例的上下文中是正确的,而不是在关于reduce 可破坏性的一般问题中。 .

问题:

但是reducecollect 中是否有一种方法可以过早地“中断”,而无需遍历所有流元素? (这意味着我需要在迭代时累积状态,所以我使用reducecollect)。

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

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

使用 cmets 的代码示例:

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

}

【问题讨论】:

  • 你能详细说明一下,“累积状态”是什么意思,我需要返回最后传递的元素
  • 并非所有循环都最好用流替换。如果你有这样的行为,它可能会更清楚地作为一个循环(即使有某种方法可以让 Stream 做你想做的事)
  • 添加代码示例

标签: java java-8 java-stream reduce collect


【解决方案1】:

给定一个数组作为输入,在我看来你正在寻找这样的东西:

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

【讨论】:

  • 非常好,学到了很多!但我不能接受这个作为答案,因为过滤器进行了整个循环......(在第一个示例中),在第二个示例中,它是我不喜欢的二维过滤器。我的意思是,如果已经创建了正数组,我希望得到一个答案,我们不完整传递这个数组,只需转到满足条件的第一个元素,
  • @VladimirNabokov 不,它不会遍历完整列表,使用findFirst() 有助于在那里(如果这就是你的意思)......第二个实现是你可以直接替换的实现你的main 方法。
  • 我必须说这是代码示例上下文中的正确答案。它解决了这个问题,但是在最初的问题的背景下,“reduce 是否可破坏?”这不是答案,答案是“牢不可破”(可能)。
【解决方案2】:

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

整数列表:

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

让我们找到第 i 个元素的值,其中 element[i+1]-element[i] &gt; 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}

第一步:

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

第二步

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>

【讨论】:

  • i1 和 i2 是 lambda 表达式的占位符。这些只是一对最近的元素。在答案中添加了一些示例。
  • 那么,在您的示例中,我们确实传递了流中的所有元素,直到最后?而且我理解逻辑(有点),我只是无法找到为什么将其解释为一对相邻元素......所有这些都正式化了吗?我看到使用的 API,但你的意思是我们总是在这里默认谈论一对邻居:Optional reduce(BinaryOperator accumulator)。我喜欢你的回答,但我不能说这是解决问题的方法,但是我学到了一些东西。
  • 是的,所有元素,因为正如我之前写的,在 Streams 中没有中断条件。为什么它将 i1 和 i2 解释为邻居元素的最简单的答案是:当您迭代集合时 - 您逐个元素地获取元素。正确的?这是同样的情况,除了有一刻——你一对一对,一对一对。因此,如果您的收藏已排序 - 它将与差异元素最近。对不起,如果我写的不是那么清楚,我的解释能力很弱:)
  • Danila,i1 和 i2 是占位符,但它们是移动占位符,i1 总是包含归约的结果,i2 是下一个元素。我只是无法理解,他们是如何从一开始就将 i1 初始化为 0...在开始时没有默认值减少...
猜你喜欢
  • 1970-01-01
  • 2014-06-26
  • 2017-03-28
  • 2015-07-18
  • 2012-01-24
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
相关资源
最近更新 更多