【问题标题】:Get last n elements from stream从流中获取最后 n 个元素
【发布时间】:2015-05-27 07:42:53
【问题描述】:

我想知道有没有替代方法

List<X> lastN = all.subList(Math.max(0, all.size() - n), all.size());

使用stream?

【问题讨论】:

  • 我不认为这对于流来说通常是可能的,因为流的大小可能不知道先验,甚至可能是无限的。如果您从列表创建流,只需像您一样使用子列表。
  • @tobias_k OP 似乎有一个有限的列表但是......
  • 如果您已经有一个列表,那么subList 是要走的路。然后,您可以复制、流式传输,随心所欲。

标签: java java-8 java-stream


【解决方案1】:

使用Stream.skip()

在丢弃流的前 n 个元素后,返回由该流的剩余元素组成的流。如果此流包含少于 n 个元素,则将返回一个空流。

all.stream().skip(Math.max(0, all.size() - n)).forEach(doSomething);

【讨论】:

  • 这不会返回最后 n 个元素。它跳过第一个 n。不是 OP 要求的。
  • 如果我们不知道流大小怎么办(假设最坏的情况)?
  • 如果all.size() &lt; n,则会抛出IllegalArgumentException。这里也需要原始问题中的Math.max(0, all.size() - n)。
【解决方案2】:

自定义收集器可以这样写:

public static <T> Collector<T, ?, List<T>> lastN(int n) {
    return Collector.<T, Deque<T>, List<T>>of(ArrayDeque::new, (acc, t) -> {
        if(acc.size() == n)
            acc.pollFirst();
        acc.add(t);
    }, (acc1, acc2) -> {
        while(acc2.size() < n && !acc1.isEmpty()) {
            acc2.addFirst(acc1.pollLast());
        }
        return acc2;
    }, ArrayList::new);
}

并像这样使用它:

List<String> lastTen = input.stream().collect(lastN(10));

【讨论】:

  • 你不需要写ArrayList&lt;T&gt;::new,只需要ArrayList::new就足够了,因为方法引用总是使用类型推断而不是原始类型(比如在使用@时总是存在“钻石运算符” 987654325@)。您已经将它与ArrayDeque::new 一起使用。顺便提一句。我更喜欢removeFirst/removeLast 而不是pollFirst/pollLast 这里...
  • @Holger,首先我写了ArrayList::new,但 Eclipse 显示了关于未检查构造函数的警告。嗯,这可能是 ECJ 特有的问题。
  • 有趣的是,ArrayDeque::new 将受益于类型见证,因为使用 ArrayDeque&lt;T&gt;::new 将使Collector.of 调用中的类型见证过时(并且&lt;T&gt; 比&lt;T, Deque&lt;T&gt;, List&lt;T&gt;&gt; 更简单)而ArrayList&lt;T&gt;::new 的类型见证不是必需的,因为它的类型可以从目标类型中推断出来。
【解决方案3】:

如果流的大小未知,则可能无法绕过消耗整个流并缓冲到目前为止遇到的最后一个 n 元素。您可以使用某种双端队列或专门的环形缓冲区来自动保持其最大大小(请参阅this related question 了解一些实现)。

public static <T> List<T> lastN(Stream<T> stream, int n) {
    Deque<T> result = new ArrayDeque<>(n);
    stream.forEachOrdered(x -> {
        if (result.size() == n) {
            result.pop();
        }
        result.add(x);
    });
    return new ArrayList<>(result);
}

所有这些操作(size、pop、add)都应该具有 O(1) 的复杂度,因此(未知)长度的流的整体复杂度 n 将是 O(n)。

【讨论】:

    【解决方案4】:

    有时我需要一个“oneliner”(在这种情况下是一个三衬),因为创建一个收集器太麻烦了。

    如果流很小,则可以再次reverse、limit 和reverse 而不会牺牲太多性能。这将产生最后 n 个元素。

    如果需要过滤,这很有用,因为在这种情况下无法指定大小。

    Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
      .filter(i -> i % 2 == 0)
      .sorted(Comparator.reverseOrder())
      .limit(2)
      .sorted(Comparator.naturalOrder())
      .forEach(System.out::println); // prints 6 8
    

    【讨论】:

    • 第二次排序不应该使用自然顺序而不是逆序吗?
    • 是的,你是对的。我更新了答案。另外值得注意的是,我的这个解决方案适用于有序列表。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-14
    • 1970-01-01
    • 2013-06-19
    • 2011-05-12
    • 2023-01-02
    • 2017-08-31
    相关资源
    最近更新 更多