【问题标题】:arg max in Java 8 streams?Java 8流中的arg max?
【发布时间】:2014-12-22 16:04:44
【问题描述】:

我经常需要根据产生 double 或 int 值的标准的最大化来获取集合的最大元素。 Streams 有 max() 函数,它需要我实现一个比较器,我觉得这很麻烦。有没有更简洁的语法,比如下面例子中的names.stream().argmax(String::length)

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class ArgMax
{
    public static void main(String[] args)
    {
        List<String> names = Arrays.asList("John","Joe","Marilyn");
        String longestName = names.stream().max((String s,String t)->(Integer.compare(s.length(),t.length()))).get();
        System.out.println(longestName);
    }
}

【问题讨论】:

  • 我是否遗漏了什么或者下面的解决方案都没有实际返回 Argmax()。他们应该返回最大值的index,而不是值本身。这是一个非常不同的问题。 en.wikipedia.org/wiki/Arg_max

标签: java java-8 java-stream argmax


【解决方案1】:

使用

String longestName = names.stream().max(Comparator.comparing(String::length)).get();

比较某些属性上的元素(可能比这更复杂,但不是必须的)。

As Brian suggests in the comments,如果Stream 可能为空,则像这样使用Optional#get() 是不安全的。您会更适合使用一种更安全的检索方法,例如Optional#orElse(Object),如果没有最大值,它将为您提供一些默认值。

【讨论】:

  • 啊!不要在最后使用 raw get(),否则如果流为空,您将抛出 NSEE。使用orElseorElseThrowifPresent 等安全方法。
  • @BrianGoetz 我想我们已经发现了 Brian 的一种过敏症。每当我写Optional.get() 时,他都会打喷嚏。
  • @StuartMarks 确实如此。我们不应该称它为get。我们应该称它为getOrThrow()orElseThrow()getUnsafely() 或者可能只是getOrThrowNoSuchElementExcpetionIfThisOptionalIsNotPresent()
  • 在这种情况下如何获取longestName值的索引?我认为names.indexOf(longestName) 可以吗?
  • @zygimantus:如果你可以接受第二次线性搜索,是的。否则,首先使用IntStream.range(0, names.length()).boxed().max(Comparator.comparingInt(ix -&gt; names.get().length()) 搜索索引。
【解决方案2】:

我认为应该考虑到虽然max/min 是独一无二的,但argMax/argMin 当然不能保证这一点;这尤其意味着归约的类型应该是一个集合,例如List。这需要比上面建议的更多的工作。

下面的ArgMaxCollector&lt;T&gt; 类提供了这种缩减的简单实现。 main 显示了此类用于计算字符串集的 argMax/argMin 的应用程序

one two three four five six seven

按长度排序。输出(分别报告argMaxargMin 收集器的结果)应该是

[three, seven]
[one, two, six]

分别是两个最长和三个最短的字符串。

这是我第一次尝试使用新的 Java 8 流 API,因此欢迎任何评论!

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collector;

class ArgMaxCollector<T> {

    private T max = null;
    private ArrayList<T> argMax = new ArrayList<T>();
    private Comparator<? super T> comparator;

    private ArgMaxCollector( Comparator<? super T> comparator ) {
        this.comparator = comparator;
    }

    public void accept( T element ) {
        int cmp = max == null ? -1 : comparator.compare( max, element );
        if ( cmp < 0 ) {
            max = element;
            argMax.clear();
            argMax.add( element );
        } else if ( cmp == 0 )
            argMax.add( element );
    }

    public void combine( ArgMaxCollector<T> other ) {
        int cmp = comparator.compare( max, other.max );
        if ( cmp < 0 ) {
            max = other.max;
            argMax = other.argMax;
        } else if ( cmp == 0 ) {
            argMax.addAll( other.argMax );
        }
    }

    public List<T> get() {
        return argMax;
    }

    public static <T> Collector<T, ArgMaxCollector<T>, List<T>> collector( Comparator<? super T> comparator ) {
        return Collector.of(
            () -> new ArgMaxCollector<T>( comparator ),
            ( a, b ) -> a.accept( b ),
            ( a, b ) ->{ a.combine(b); return a; },
            a -> a.get() 
        );
    }
}

public class ArgMax {

    public static void main( String[] args ) {

        List<String> names = Arrays.asList( new String[] { "one", "two", "three", "four", "five", "six", "seven" } );

        Collector<String, ArgMaxCollector<String>, List<String>> argMax = ArgMaxCollector.collector( Comparator.comparing( String::length ) );
        Collector<String, ArgMaxCollector<String>, List<String>> argMin = ArgMaxCollector.collector( Comparator.comparing( String::length ).reversed() );

        System.out.println( names.stream().collect( argMax ) );
        System.out.println( names.stream().collect( argMin ) );

    }

}

【讨论】:

    【解决方案3】:

    这里简单有效的解决方案:

    https://stackoverflow.com/a/63201174/2069400

    /** return argmin item, else null if none.  NAN scores are skipped */
    public static <T> T argmin(Stream<T> stream, ToDoubleFunction<T> scorer) {
        Double min = null;
        T argmin = null;
        for (T p: (Iterable<T>) stream::iterator) {
            double score = scorer.applyAsDouble(p);
            if (min==null || min > score) {
                min = score;
                argmin = p;
            }
        }
        return argmin;
    }
    

    【讨论】:

    • +1。这最大限度地减少了对scorer 的调用次数。它称它为n 次。相比之下,Stream::min() 调用了Comparator.comparing (n-1)*2 次。其中n 是流中的元素数。如果比较器很昂贵,这非常重要。
    猜你喜欢
    • 1970-01-01
    • 2019-03-22
    • 2014-04-28
    • 2019-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-23
    • 1970-01-01
    相关资源
    最近更新 更多