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

2022-08-31 11:37:47

在前面的问题中 [ 如何在 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简洁地表达上述情况有哪些更好的方法?


答案 1

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

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

list.subList(3, 7);

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

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。

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


答案 2

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

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

要做到这一点,你必须使用一种与我回答另一个问题时类似的技术。也就是说,您必须将元素收集到列表中,以便能够获得元素的计数,可能是在完成一些上游筛选之后。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)));
    };
}

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


推荐