In a previous question [ How to dynamically do filtering in Java 8? ] Stuart Marks gave a wonderful answer, and provided several useful utilities to handle selection of topN
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.
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.