javajava-stream

Concise way to get both min and max value of Java 8 stream


Is there a concise way to extract both the min and max value of a stream (based on some comparator) in one pass?

There appear to be many ways to get the min and max values individually, or I can sort the stream into a temporary object, for example:

List<T> sorted = Stream.of(...).sorted().collect(Collectors.toList());
T min = sorted.get(0);
T max = sorted.get(sorted.size() - 1);

But this isn't concise and requires allocating a temporary object. I'd rather not allocate a temporary object or make two passes through the stream. Is there an alternative?

Pair<T> extent = Stream.of(...).???

Solution

  • If this is a frequently needed feature, we better make a Collector to do the job. We'll need a Stats class to hold count, min, max, and factory methods to creat stats collector.

    Stats<String> stats = stringStream.collect(Stats.collector())
    
    fooStream.collect(Stats.collector(fooComparator))
    

    (Maybe a better convenience method would be Stats.collect(stream))

    I made an example Stats class -

    https://gist.github.com/zhong-j-yu/ac5028573c986f7820b25ea2e74ed672

    public class Stats<T>
    {
        int count;
    
        final Comparator<? super T> comparator;
        T min;
        T max;
    
        public Stats(Comparator<? super T> comparator)
        {
            this.comparator = comparator;
        }
    
        public int count(){ return count; }
    
        public T min(){ return min; }
        public T max(){ return max; }
    
        public void accept(T val)
        {
            if(count==0)
                min = max = val;
            else if(comparator.compare(val, min)<0)
                min = val;
            else if(comparator.compare(val, max)>0)
                max = val;
    
            count++;
        }
    
        public Stats<T> combine(Stats<T> that)
        {
            if(this.count==0) return that;
            if(that.count==0) return this;
    
            this.count += that.count;
            if(comparator.compare(that.min, this.min)<0)
                this.min = that.min;
            if(comparator.compare(that.max, this.max)>0)
                this.max = that.max;
    
            return this;
        }
    
        public static <T> Collector<T, Stats<T>, Stats<T>> collector(Comparator<? super T> comparator)
        {
            return Collector.of(
                ()->new Stats<>(comparator),
                Stats::accept,
                Stats::combine,
                Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH
            );
        }
    
        public static <T extends Comparable<? super T>> Collector<T, Stats<T>, Stats<T>> collector()
        {
            return collector(Comparator.naturalOrder());
        }
    }