【问题标题】:Concise way to get both min and max value of Java 8 stream获取 Java 8 流的最小值和最大值的简洁方法
【发布时间】:2023-03-30 22:12:02
【问题描述】:

是否有一种简洁的方法可以一次性提取流的最小值和最大值(基于某个比较器)?

似乎有很多方法可以单独获取最小值和最大值,或者我可以将流排序为临时对象,例如:

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(...).???

【问题讨论】:

  • 你考虑过像IntSummaryStatistics这样的收藏家吗?假设这与数字无关,您可以遵循该模式。

标签: java java-stream


【解决方案1】:

如果你有一个整数流,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)));

【讨论】:

  • 我喜欢那个!
【解决方案2】:

如果这是一个经常需要的功能,我们最好创建一个Collector 来完成这项工作。我们需要一个 Stats 类来保存 count, min, max,以及创建统计收集器的工厂方法。

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

fooStream.collect(Stats.collector(fooComparator))

(也许更方便的方法是Stats.collect(stream)

我做了一个例子Stats类-

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());
    }
}

【讨论】:

  • 我不会指定UNORDERED,因为这个收集器能够遵守遭遇顺序,即如果有平局,则提供最大/最小元素中的第一个,就像max(…) 和@ 987654331@做。
  • IntSummaryStatistics 更好
【解决方案3】:

将流的每个元素映射为一对,其中两个元素分别代表最小值和最大值;然后通过取最小值的最小值和最大值的最大值来减少对。

例如,使用一些Pair 类和一些Comparator&lt;T&gt;

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));

【讨论】:

  • 不像我希望的那样简洁,但这看起来不错。如果有 Comparator.min() 和 Comparator.max() 来简化最后两行,那就太好了。
  • 番石榴中有一对吗?
  • Guava 没有一对。
  • 你说的是 Apache Commons 吗?
  • 糟糕。好吧,实现并不是特别相关。我就把它留在“一些Pair”。
【解决方案4】:

我认为你需要那个

IntStream myIntStream = IntStream.rangeClosed(1, 100);
IntSummaryStatistics intStatistic = myIntStream.summaryStatistics();

System.out.println("Max: " + intStatistic.getMax() + " Min: " + intStatistic.getMin());

【讨论】:

    【解决方案5】:

    从 Java 12 开始,您可以使用 Collectors::teeing 一次获得两个或更多结果:

    class Movie {
        String title;
        double rating;
        //...
    }
    
    class Pair<T1, T2> {
        T1 left;
        T2 right;
        //...
    }
    
    @Test
    void shouldFindWorstAndBestMovie() {
        var m1 = new Movie("Groundhog Day", 8);
        var m2 = new Movie("Stop! Or My Mom Will Shoot", 4.4);
        var m3 = new Movie("Forrest Gump", 8.8);
    
        var ratingComparator = Comparator.comparing(Movie::getRating);
    
        Pair<Movie, Movie> result = Stream.of(m1, m2, m3)
                .collect(Collectors.teeing(
                        Collectors.minBy(ratingComparator),
                        Collectors.maxBy(ratingComparator),
                        (min, max) -> new Pair<>(min.orElse(null), max.orElse(null))
                ));
    
        assertEquals(m2, result.getLeft(), "min does not match");
        assertEquals(m3, result.getRight(), "max does not match");
    }
    

    您可以在this article 中找到更多详细信息和示例。

    【讨论】:

      【解决方案6】:

      对于相当简洁的纯 Java 解决方案,您可以使用 .peek()。这不是真正的功能,因为 .peek() 所做的任何事情都是副作用。但这确实一次性完成,不需要排序并且不太冗长。有一个“临时”对象,即 AtomicRef,但您可能会分配一个本地 var/ref 来保存最小值和最大值。

      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()
      

      【讨论】:

      • 嗯...这依赖于max 必须消耗整个source-stream - 我不确定以任何方式保证(考虑排序的源,短路可能可能吗?)。
      • OP 正在对原始问题进行排序并希望避免它。是什么让您相信无法保证消费流? .max(cmp) 和 .peek() 都是在 java.util.stream.Stream 接口上定义的,并且在管道处理期间抛出的异常之外没有任何东西可以防止这种情况发生......
      • 我同意这种方法适用于当前版本 - 我只是想知道它是否可以保证在未来的版本中继续工作(例如,参见 my question regarding Stream.count,它将不再访问java 9,如果它可以以更有效的方式确定大小)。但是使用自定义Comparator,这样的优化在这里可能无论如何都是不可能的。
      • 根据 J9 API,max() 是终端,但不是短路。我无法想象一个在采用排序流的max() 之外短路的实现(OP 想要避免)。
      【解决方案7】:

      使用任何可变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;
          }
      });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-18
        • 2014-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-25
        • 1970-01-01
        • 2014-09-04
        相关资源
        最近更新 更多