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

Fra*_*ank 57 java lambda filter java-8

在之前的问题中[ 如何在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));
    };
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

topNFromRange(Comparator<Widget> cmp, long from, long to) = topNFromRange(comparing(Widget::length), 3L, 7L)
Run Code Online (Sandbox Code Playgroud)

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

我能想到的最简单的方法是从原版获得前7 [T7],从原版获得前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)
Run Code Online (Sandbox Code Playgroud)

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

我能想到的最简单的方法是从原版获得前30%[TP30],从原版获得前10%[TP10],然后获得TP30 - TP10.

有什么更好的方法可以使用Java 8 Lambda简明扼要地表达上述情况?

ski*_*iwi 50

要从a获得范围Stream<T>,您可以使用skip(long n)先跳过一定数量的元素,然后您可以调用limit(long n)仅拍摄特定数量的项目.

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

list.subList(3, 7);
Run Code Online (Sandbox Code Playgroud)

现在有了a Stream,你需要首先跳过3个项目,然后取7 - 3 = 4个项目,所以它变为:

stream.skip(3).limit(4);
Run Code Online (Sandbox Code Playgroud)

作为@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)))
    );
}
Run Code Online (Sandbox Code Playgroud)

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

这将打印元素10到29.

它的工作原理是使用一个Collector<T, ?, Stream<T>>从流中获取元素,将它们转换为a List<T>,然后获取a Stream<T>,对其进行排序并将(正确的)边界应用于它.


Stu*_*rks 42

用户skiwi已经回答了问题的第一部分.第二部分是:

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

要做到这一点,你必须使用与topPercent我对另一个问题的答案类似的技巧.也就是说,您必须将元素收集到列表中,以便能够获得元素的计数,可能在完成某些上游过滤之后.

一旦你的计数,然后你计算出正确的值skip,并limit基于计数和您想要的百分比.像这样的东西可能会起作用:

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)));
    };
}
Run Code Online (Sandbox Code Playgroud)

当然,您必须对from和进行错误检查to.一个更微妙的问题是确定要发射的元素数量.例如,如果您有十个元素,则它们位于索引[0..9],对应于0%,10%,20%,...,90%.但是如果你要求的范围是9%到11%,那么上面的代码根本不会发出任何元素,而不会像你期望的那样发出10%的元素.所以一些修补百分比计算可能是必要的,以适应你想要做的事情的语义.