如何使用 Java8lambda 从流中获取一系列项目?

在之前的问题[ 如何在 Java8中动态地进行过滤?]中,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] How to get top items from 3 to 7 from a stream with certain amount of items, so if the stream has items from A1, A2 .. A10, the call to

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

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

我能想到的最简单的方法是从原始文件中获取 top 7[ T7] ,从原始文件中获取 top 3[ T3] ,然后获取 T7-T3。

[2] How to get top items from top 10% to top 30% from a stream with certain amount of items, so if the stream has items from X1, X2 .. X100, the call to

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。

有什么更好的方法可以使用 Java8Lambda 简洁地表达上述情况?

120904 次浏览

To get a range from a Stream<T>, you can use skip(long n) to first skip a set number of elements, and then you can call limit(long n) to only take a specific amount of items.

Consider a stream with 10 elements, then to get elements 3 to 7, you would normally call from a List:

list.subList(3, 7);

Now with a Stream, you need to first skip 3 items, and then take 7 - 3 = 4 items, so it becomes:

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

As a variant to @StuartMarks' solution to the second answer, I'll offer you the following solution which leaves the possibility to chain intact, it works similar to how @StuartMarks does it:

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)))
);
}

and

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

This will print the elements 10 through 29.

It works by using a Collector<T, ?, Stream<T>> that takes in your elements from the stream, transforms them into a List<T>, then obtains a Stream<T>, sorts it and applies the (correct) bounds to it.

User skiwi already answered the first part of the question. The second part is:

(2) How to get top items from top 10% to top 30% from a stream with certain amount of items....

To do this, you have to use a similar technique as topPercent in my answer to the other question. That is, you have to collect the elements into a list in order to be able to get a count of the elements, possibly after some upstream filtering has been done.

Once you have the count, then you compute the right values for skip and limit based on the count and the percentages you want. Something like this might work:

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)));
};
}

Of course you will have to do error checking on from and to. A more subtle problem is determining how many elements to emit. For example, if you have ten elements, they are at indexes [0..9], which correspond to 0%, 10%, 20%, ..., 90%. But if you were to ask for a range from 9% to 11%, the above code would emit no elements at all, not the one at 10% like you might expect. So some tinkering with the percentage computations is probably necessary to fit the semantics of what you're trying to do.