How to get a range of items from stream using Java 8 lambda?

后端 未结 2 1870
一向
一向 2020-12-07 23:56

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

相关标签:
2条回答
  • 2020-12-08 00:37

    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.

    0 讨论(0)
  • 2020-12-08 00:45

    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.

    0 讨论(0)
提交回复
热议问题