【问题标题】:Lazy sorted() in Java8 Streams, need for resorting at each iterationJava8 Streams中的Lazy sorted(),每次迭代都需要重新排序
【发布时间】:2015-03-22 23:26:45
【问题描述】:

我正在寻找一种方法来模拟 Java 8 流的以下行为。 给定一个年份流,对它们进行排序,以便输出前 10 个值,这样 输出一年后,减少,迭代重新开始:

如果我输入 2005、2020、2000、1967 和 2018 年,我预计以下结果(限制为 10):

2020
2019
2018 2018  
2017 2017
2016 2016 2016
2015 ...

我正在使用的测试是:

public class LazyTest {

    public static void main(String[] args) {
        Stream.of(2005,2020,2000,1967,2018)
              .map(YearWrapper::new)
              .sorted()
              .limit(10)
              .peek(year -> year.decreaseYear())
              .forEach(System.out::println);
    }

    public static class YearWrapper implements Comparable<YearWrapper>{
        private int currentYear;

        public YearWrapper(int year){
            this.currentYear=year;
        }
        public void decreaseYear(){
            currentYear--;
        }
        @Override
        public int compareTo(YearWrapper yearsToCompare) {
            return Integer.compare(yearsToCompare.currentYear, this.currentYear);
        }

        @Override
        public String toString(){
            return String.valueOf(currentYear);
        }
    }
}

但似乎 sorted() 一点也不懒惰。整个排序在开始时进行一次,因此在任何进一步操作之前计算顺序,因此,示例的 5 个值是按顺序传递的,因此 reduce() 对迭代没有实际影响。

有没有办法让 sorted() 变得惰性并在流式传输下一个元素之前再次应用于剩余元素?

任何其他接近的方法将不胜感激!

【问题讨论】:

  • 我没有得到输出。为什么2018年印了2次,2016年印了3次?
  • 因为我预计 sorted 会在任何过滤元素改变其状态后进行操作,所以 2020 会减少并单独打印,直到轮到 2018 年
  • 所以你想减少一个值直到它等于下一个值,然后你减少两个值直到它们等于第三个等等?在每次迭代期间,您打印出正在减少的值?限制会影响源值还是目标值?
  • Misha 的回答是我落后的,只是我不想读取对象而是改变它的状态,所以队列在后面

标签: sorting lambda java-8 lazy-evaluation java-stream


【解决方案1】:

documentation of Stream.sorted() 说:

这是stateful intermediate operation

又是described as

有状态操作可能需要在产生结果之前处理整个输入。例如,在查看流的所有元素之前,无法通过对流进行排序产生任何结果。

这记录了排序的非惰性性质,但是,这与您的问题无关。即使排序是惰性的,但它并没有改变流的基本原则,即每个项目最多一次流到终端操作。

您说您希望排序“是懒惰的”,但您实际上期望的是在消费后对每个项目再次进行排序,这意味着对 n 元素流进行排序实际上意味着将 n 个项目排序 n 次,这是其他人都不会想到的,尤其是 peek 并不意味着会产生影响正在进行的操作的副作用。

【讨论】:

  • 你已经有一个working solution 但我想解释一下你第一次尝试失败的背景......
【解决方案2】:

我是否正确地理解你,你想从列表中取出最大的数字,递减它,把它放回列表中,重复 10 次?

如果是这样,那么这是PriorityQueue 的工作:

PriorityQueue<Integer> queue = Stream.of(2005,2020,2000,1967,2018)
        .collect(toCollection(() -> new PriorityQueue(reverseOrder())));

Stream.generate(() -> {
    Integer year = queue.poll();
    queue.add(year - 1);
    return year;
}).limit(10).forEach(System.out::println);

【讨论】:

  • 很棒的答案。它为我指出了完美的解决方案。我尝试使用 peek() 而不是 poll 并更改对象的状态,它更有可能是我所期待的,但现在它永远不会返回重复项。我的意思是,如果在某个时候最大值是 2020 年减少为 2018 年和原来的 2018 年,它应该返回两倍于 2020,2019,2018,2018,2017,2017,2016...
  • 好的,我明白了,在查看时,队列不会使用,所以这种行为是预期的,并且针对轮询和重新添加进行了优化
  • @user1352530 没错。 PriorityQueue 无法知道您弄乱了对象的状态。即使它可以检测到更改,它“重新排序”队列的方式也是删除并重新添加对象。它并不是每次都对列表​​进行排序。相反,它以一种非常巧妙的方式 (en.wikipedia.org/wiki/Heap_%28data_structure%29) 存储它,允许有效的插入和优先级检索。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-06
  • 2016-08-19
  • 2021-07-19
  • 2020-08-28
  • 1970-01-01
  • 2017-05-23
  • 1970-01-01
相关资源
最近更新 更多