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

在上一个问题中[如何在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}

我能想到的最简单的方法是从原始文件中获取前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)

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

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

有什么更好的方法来使用Java 8 Lambda简洁地表达上述情况?

Frank asked 2019-11-08T13:03:29Z
2个解决方案
48 votes

要获得Collector<T, ?, Stream<T>>的范围,可以使用List<T>先跳过一组元素,然后再调用Stream<T>以仅接收特定数量的项目。

考虑一个包含10个元素的流,然后要获得3到7个元素,通常可以从Collector<T, ?, Stream<T>>进行调用:

list.subList(3, 7);

现在使用Collector<T, ?, Stream<T>>,您需要先跳过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<T, ?, Stream<T>>进行工作,该元素从流中获取元素,将其转换为List<T>,然后获得Stream<T>,对其进行排序并对其应用(正确的)边界。

skiwi answered 2019-11-08T13:05:23Z
42 votes

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

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

为此,您必须在我对另一个问题的回答中使用与from类似的技术。 也就是说,您必须将元素收集到一个列表中,以便可能在完成一些上游筛选之后获得元素的计数。

有了计数后,便可以根据计数和所需的百分比为fromto计算正确的值。 这样的事情可能会起作用:

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进行错误检查。一个更细微的问题是确定要发射多少个元素。 例如,如果您有十个元素,它们的索引为[0..9],分别对应于0%,10%,20%,...,90%。 但是,如果您要求的范围是9%到11%,那么上面的代码将根本不发出任何元素,而不像您期望的那样发出10%的元素。 因此,可能需要对百分比计算进行一些修补,以适合您要尝试执行的语义。

Stuart Marks answered 2019-11-08T13:04:17Z
translate from https://stackoverflow.com:/questions/22917270/how-to-get-a-range-of-items-from-stream-using-java-8-lambda