Краткий способ получить как минимальное, так и максимальное значение потока Java 8

Есть ли сжатый способ извлечь как минимальное, так и максимальное значение потока (на основе некоторого компаратора) за один проход?

Кажется, существует много способов получить значения min и max отдельно или я могу сортировать поток во временном объекте, например:

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

Но это не кратким и требует выделения временного объекта. Я бы предпочел не выделять временный объект или сделать два прохода через поток. Есть ли альтернатива?

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

Ответ 1

Если это часто требуется, нам лучше сделать Collector для выполнения задания. Нам понадобится класс Stats для хранения методов count, min, max и factory для сборщика статистических данных.

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

fooStream.collect(Stats.collector(fooComparator))

(Возможно, лучший метод удобства был бы Stats.collect(stream))

Я сделал пример 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());
    }
}

Ответ 2

Коллекционер summarizingInt хорошо работает, если у вас есть поток целых чисел.

IntSummaryStatistics stats = Stream.of(2,4,3,2)
      .collect(Collectors.summarizingInt(Integer::intValue));

int min = stats.getMin();
int max = stats.getMax();

Если у вас есть удвои, вы можете использовать коллекционер summarizingDouble.

DoubleSummaryStatistics stats2 = Stream.of(2.4, 4.3, 3.3, 2.5)
  .collect(Collectors.summarizingDouble((Double::doubleValue)));

Ответ 3

Сопоставьте каждый элемент потока с парой, где два элемента представляют собой min и max; а затем уменьшаем пары, беря min min и max max.

Например, используя некоторый класс Pair и некоторый Comparator<T>:

Comparator<T> comparator = ...;
Optional<Pair<T, T>> minMax = list.stream()
    .map(i -> Pair.of(i /* "min" */, i /* "max" */))
    .reduce((a, b) -> Pair.of(
        // The min of the min elements.
        comparator.compare(a.first, b.first) < 0 ? a.first : b.first,
        // The max of the max elements.
        comparator.compare(a.second, b.second) > 0 ? a.second : b.second));

Ответ 4

Прямой подход с использованием любого изменяемого класса Pair:

final Pair<T, T> pair = new Pair<>();
final Comparator<T> comparator = ...;
Stream.of(...).forEachOrdered(e -> {
    if(pair.first == null || comparator.compare(e, pair.first) < 0){
        pair.first = e;
    }
    if(pair.second == null || comparator.compare(e, pair.second) > 0){
        pair.second = e;
    }
});

Ответ 5

Для чистого решения Java, которое достаточно кратким, вы можете использовать .peek(). Это не действительно Функционально, поскольку все, что делает .peek(), является побочным эффектом. Но это делает все за один проход, не требует сортировки и не слишком многословно. Существует объект temp, AtomicRef, но вы, вероятно, выделите локальный var/ref, чтобы сохранить min и max в любом случае.

Comparator<T> cmp = ...
Stream<T> source = ...
final AtomicReference<T> min = new AtomicReference<T>();
Optional<T> max = source.peek(t -> {if (cmp.compare(t,min.get()) < 0) min.set(t);})
    .max(cmp);
//Do whatever with min.get() and max.get()