Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy sorted() in Java8 Streams, need for resorting at each iteration

I'm looking for a way to emulate the following behavior with Java 8 streams. Given a stream of years,sort them so the top 10 values are outputed, such that after outputing a year, that is decreased and the iteration restarts resorting again:

If I input years 2005, 2020, 2000, 1967 and 2018 I expect the following results for a limit of 10:

2020
2019
2018 2018  
2017 2017
2016 2016 2016
2015 ...

The test I am using is:

public class LazyTest {

    public static void main(String[] args) {
        Stream.of(2005,2020,2000,1967,2018)
              .map(YearWrapper::new)
              .sorted()
              .limit(10)
              .peek(year -> year.decreaseYear())
              .forEach(System.out::println);
    }

    public static class YearWrapper implements Comparable<YearWrapper>{
        private int currentYear;

        public YearWrapper(int year){
            this.currentYear=year;
        }
        public void decreaseYear(){
            currentYear--;
        }
        @Override
        public int compareTo(YearWrapper yearsToCompare) {
            return Integer.compare(yearsToCompare.currentYear, this.currentYear);
        }

        @Override
        public String toString(){
            return String.valueOf(currentYear);
        }
    }
}

But it seems sorted() is not lazy at all. The whole sorting is done once at the beggining so the order is calculated before any further operation and therefore, the 5 values of the example are passed ordered one by one, so decrease() have no real effect on the iteration.

Is there any way to make sorted() lazy and being applied again to the remaining elements before streaming the next one?

Any other close approach would be much appreciated!!

like image 226
Whimusical Avatar asked Jan 09 '23 16:01

Whimusical


2 Answers

The documentation of Stream.sorted() says:

This is a stateful intermediate operation.

which is in turn described as

Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream.

This documents the non-lazy nature of sorting, however, this has nothing to do with your problem. Even if sorting was lazy, it didn’t change the fundamental principle of streams that each item is streamed to the terminal operation at most once.

You said that you expected the sorting “to be lazy” but what you actually expected is the sorting to happen again for each item after consumption which would imply that sorting a stream of n elements means actually sorting n items n times which no-one else would expect, especially as peek is not meant to have a side effect that affects the ongoing operation.

like image 136
Holger Avatar answered Jan 31 '23 08:01

Holger


Do I understand you correctly that you want to take the largest number from the list, decrement it, put it back into the list, repeat 10 times?

If so, then this is a job for PriorityQueue:

PriorityQueue<Integer> queue = Stream.of(2005,2020,2000,1967,2018)
        .collect(toCollection(() -> new PriorityQueue(reverseOrder())));

Stream.generate(() -> {
    Integer year = queue.poll();
    queue.add(year - 1);
    return year;
}).limit(10).forEach(System.out::println);
like image 43
Misha Avatar answered Jan 31 '23 07:01

Misha