如何使用 Java 8 lambda 从流中获取一系列项目?

Posted

技术标签:

【中文标题】如何使用 Java 8 lambda 从流中获取一系列项目?【英文标题】:How to get a range of items from stream using Java 8 lambda? 【发布时间】:2014-05-19 23:34:45 【问题描述】:

在上一个问题 [How to dynamically do filtering in Java 8?] Stuart Marks 给出了一个精彩的答案,并提供了几个有用的实用程序来处理从流中选择 topN 和 topPercent。

我将从他的原始答案中将它们包括在此处:

@FunctionalInterface
public interface Criterion 
    Stream<Widget> apply(Stream<Widget> s);


Criterion topN(Comparator<Widget> cmp, long n) 
    return stream -> stream.sorted(cmp).limit(n);


Criterion topPercent(Comparator<Widget> cmp, double pct) 
    return stream -> 
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .limit((long)(temp.size() * pct));
    ;

我的问题是:

[1] 如何从具有一定数量项目的流中获取从 3 到 7 的***项目,因此如果流具有来自 A1、A2 .. A10 的项目,则调用

topNFromRange(Comparator<Widget> cmp, long from, long to) = topNFromRange(comparing(Widget::length), 3L, 7L)

将返回 A3, A4, A5, A6, A7

我能想到的最简单的方法是从 original 中获取前 7 [ T7 ],从 original 中获取前 3 [ T3 ],然后得到 T7 - T3。

[2] 如何从具有一定数量项目的流中获取前 10% 到前 30% 的***项目,因此如果流具有来自 X1、X2 .. X100 的项目,则调用

topPercentFromRange(Comparator<Widget> cmp, double from, double to) = topNFromRange(comparing(Widget::length), 0.10, 0.30)

将返回 X10, X11, X12, ..., X29, X30

我能想到的最简单的方法是从 original 中获取前 30% [ TP30 ],从 original 中获取前 10% [ TP10 ],然后获取 TP30 - TP10。

使用Java 8 Lambda简洁表达上述情况有哪些更好的方法?

【问题讨论】:

【参考方案1】:

要从Stream&lt;T&gt; 中获取范围,您可以使用skip(long n) 首先跳过一组元素,然后调用limit(long n) 以仅获取特定数量的项目。

考虑一个包含 10 个元素的流,然后要获取元素 3 到 7,您通常会从 List 调用:

list.subList(3, 7);

现在有了Stream,你需要先跳过3个项目,然后取7 - 3 = 4个项目,这样就变成了:

stream.skip(3).limit(4);

作为@StuartMarks 对第二个答案的解决方案的一种变体,我将为您提供以下解决方案,它可以保持完整的链接,它的工作方式类似于@StuartMarks 的做法:

private <T> Collector<T, ?, Stream<T>> topPercentFromRangeCollector(Comparator<T> comparator, double from, double to) 
    return Collectors.collectingAndThen(
        Collectors.toList(),
        list -> list.stream()
            .sorted(comparator)
            .skip((long)(list.size() * from))
            .limit((long)(list.size() * (to - from)))
    );

IntStream.range(0, 100)
        .boxed()
        .collect(topPercentFromRangeCollector(Comparator.comparingInt(i -> i), 0.1d, 0.3d))
        .forEach(System.out::println);

这将打印元素 10 到 29。

它的工作原理是使用 Collector&lt;T, ?, Stream&lt;T&gt;&gt; 从流中获取您的元素,将它们转换为 List&lt;T&gt;,然后获取 Stream&lt;T&gt;,对其进行排序并对其应用(正确的)边界。

【讨论】:

如果跳过前10%的item,那么流中只剩下90%的item,如何从原来的30%中获取items,因为90%中的30%是不是原来的 30%,对吗? @Frank 您需要提前计算这些数字。 @Frank 我已经更新了答案,还包括一个可以将流链接在一起的变体。 很有趣,感谢您的努力。那么从用户的角度来看有什么区别呢?什么时候用哪一个?任何效率/准确性差异?我可以看到涉及的步骤更多,似乎更复杂,有没有办法将其简化为形式: Criterion topPercentFromRange(Comparator cmp, double from, double to) ,更直观。 @Frank 我个人更喜欢链接方法,因为这是使用流的基本要素,我不喜欢静态方法,除非绝对必要。就我而言,这是唯一真正的区别。【参考方案2】:

用户skiwi已经answered问题的第一部分。第二部分是:

(2) 如何从具有一定数量项目的流中获取前 10% 到前 30% 的***项目....

为此,您必须在我的answer 中使用与topPercent 类似的技术来解决另一个问题。也就是说,您必须将元素收集到一个列表中,以便能够获得元素的计数,可能是在完成一些上游过滤之后。

获得计数后,您可以根据所需的计数和百分比计算 skiplimit 的正确值。这样的事情可能会奏效:

Criterion topPercentFromRange(Comparator<Widget> cmp, double from, double to) 
    return stream -> 
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .skip((long)(temp.size() * from))
                   .limit((long)(temp.size() * (to - from)));
    ;

当然,您必须对fromto 进行错误检查。一个更微妙的问题是确定要发射多少元素。例如,如果您有 10 个元素,它们位于索引 [0..9] 处,对应于 0%、10%、20%、...、90%。但是,如果您要求从 9% 到 11% 的范围,则上面的代码根本不会发出任何元素,而不是像您预期的那样在 10% 时发出。因此,可能需要对百分比计算进行一些修改以适应您正在尝试执行的操作的语义。

【讨论】:

足够接近我正在寻找的东西,我会制定详细信息,谢谢! 我已经更新了我的答案,还包括了你正在做的事情的一种形式,但是使用收集器,也许它对最初的标准问题也很有趣? @skiwi 很有趣,使用收集器的完成器功能将集合转换回流。我不确定它是否比仅仅声明一个局部变量更好或更坏。 (在这种情况下,lambda 参数被用作局部变量。)不过,这是一种有用的技术,需要牢记在心。

以上是关于如何使用 Java 8 lambda 从流中获取一系列项目?的主要内容,如果未能解决你的问题,请参考以下文章

pentaho水壶:从流中获取行数

如何从流中读取 CSV 文件并在写入时处理每一行?

iOS - 如何从流中读取音频并播放音频

NAudio在改变音高而不是文件时寻找一种从流中读取的方法

连续从流中读取?

[C#]如何使用Newton.Json从流中反序列化json数据