Java流:查找属性值为min/max的元素

Java流:查找属性值为min/max的元素,java,collections,java-stream,Java,Collections,Java Stream,我有一个对象流,我想找到一个具有某个属性的最大值的对象,该属性的计算代价很高 作为一个具体的简单示例,假设我们有一个字符串列表,我们希望找到最酷的一个,给定一个coolnessIndex函数 以下方面应起作用: String coolestString = stringList .stream() .max((s1, s2) -> Integer.compare(coolnessIndex(s1), coolnessIndex(s2))) .

我有一个对象流,我想找到一个具有某个属性的最大值的对象,该属性的计算代价很高

作为一个具体的简单示例,假设我们有一个字符串列表,我们希望找到最酷的一个,给定一个
coolnessIndex
函数

以下方面应起作用:

String coolestString = stringList
        .stream()
        .max((s1, s2) -> Integer.compare(coolnessIndex(s1), coolnessIndex(s2)))
        .orElse(null);
现在,这有两个问题。首先,假设
coolnessIndex
的计算成本很高,这可能不会很有效。我假设
max
方法需要重复使用比较器,比较器反过来会重复调用
coolnessIndex
,最后会对每个字符串调用不止一次

其次,必须提供比较器会导致代码中出现一些冗余。我更喜欢这样的语法:

String coolestString = stringList
        .stream()
        .maxByAttribute(s -> coolnessIndex(s))
        .orElse(null);

但是,我在
API中找不到匹配的方法。这让我感到惊讶,因为通过属性查找min/max似乎是一种常见的模式。我想知道是否有比使用比较器(for循环除外)更好的方法。

使用两个流如何,一个流使用预先计算的值创建贴图,另一个流使用贴图的条目集来查找最大值:

String coolestString=stringList
.stream()
.collect(Collectors.toMap(Function.identity(),Test::coolnessIndex))
.entrySet()
.stream()
.max((s1,s2)->Integer.compare(s1.getValue(),s2.getValue())
.orElse(空)
.getKey();

这是一个缩减问题。将列表缩减为特定值。通常,reduce在列表中对部分解决方案和列表中的一项进行操作。在这种情况下,这意味着将以前的“获胜”值与列表中的新值进行比较,该列表将在每次比较时计算两次昂贵的操作

另一种方法是使用收集而不是减少

自定义类将允许跟踪昂贵的操作,因为它减少了列表。消费者可以通过使用可变状态来绕过对昂贵计算的多次调用

类冷却器实现消费者{
字符串最酷字符串=”;
int coolestValue=0;
公共字符串最酷(){
返回最冷的字符串;
}
@凌驾
公共无效接受(字符串arg0){
联合收割机(arg0,昂贵的(arg0));
}
专用void合并(字符串其他,int-exp){
如果(最冷值
这个类接受一个字符串,如果它比前一个优胜者更酷,它将替换它并保存昂贵的计算值

Cooler=Stream.of(“java”、“php”、“clojure”、“c”、“lisp”)
.收集(冷却器:新建、冷却器:接受、冷却器:合并);
System.out.println(cooler.coolest());

我将创建一个本地类(在方法中定义的类很少见,但完全合法),并将您的对象映射到该类,这样每个对象的昂贵属性将精确计算一次:

class IndexedString {
    final String string;
    final int index;

    IndexedString(String s) {
        this.string = Objects.requireNonNull(s);
        this.index = coolnessIndex(s);
    }

    String getString() {
        return string;
    }

    int getIndex() {
        return index;
    }
}

String coolestString = stringList
    .stream()
    .map(IndexedString::new)
    .max(Comparator.comparingInt(IndexedString::getIndex))
    .map(IndexedString::getString)
    .orElse(null);

您可以适当地利用从流中收集结果的想法。昂贵的冷却计算功能的约束使您考虑对流的每个元素调用该函数一次。< /P> Java8在
上提供了
collect
方法以及使用收集器的各种方式。如果您使用
TreeMap
收集结果,似乎可以保留表达能力,同时兼顾效率:

public class Expensive {
    static final Random r = new Random();
    public static void main(String[] args) {
        Map.Entry<Integer, String> e =
        Stream.of("larry", "moe", "curly", "iggy")
                .collect(Collectors.toMap(Expensive::coolness,
                                          Function.identity(),
                                          (a, b) -> a,
                                          () -> new TreeMap<>
                                          ((x, y) -> Integer.compare(y, x))
                        ))
                .firstEntry();
        System.out.println("coolest stooge name: " + e.getKey() + ", coolness: " + e.getValue());
    }

    public static int coolness(String s) {
        // simulation of a call that takes time.
        int x = r.nextInt(100);
        System.out.println(x);
        return x;
    }
}
公共类{
静态最终随机r=新随机();
公共静态void main(字符串[]args){
地图入口e=
“拉里”、“莫伊”、“卷发”、“伊吉”的流
.collect(收集器)toMap(昂贵的::凉爽,
Function.identity(),
(a,b)->a,
()->新树形图
((x,y)->整数。比较(y,x))
))
.firstEntry();
System.out.println(“最酷的工具名:”+e.getKey()+”,酷:+e.getValue());
}
公共静态整数凉爽度(字符串s){
//模拟需要时间的呼叫。
int x=r.nextInt(100);
系统输出println(x);
返回x;
}
}

此代码以最大冷却度打印
stooge
,并且对每个
stooge
调用
coolness
方法一次。可以进一步改进用作
合并函数的
二进制运算符
(a,b)->a

这里有一个变体,它使用
对象[]
作为元组,不是最漂亮的代码,而是简洁的代码

String coolestString = stringList
        .stream()
        .map(s -> new Object[] {s, coolnessIndex(s)})
        .max(Comparator.comparingInt(a -> (int)a[1]))
        .map(a -> (String)a[0])
        .orElse(null);

谢谢大家的建议。最后我找到了我最喜欢的解决方案——bayou.io的答案:

有一个通用的
缓存
方法:

public static <K,V> Function<K,V> cache(Function<K,V> f, Map<K,V> cache)
{
    return k -> cache.computeIfAbsent(k, f);
}

public static <K,V> Function<K,V> cache(Function<K,V> f)
{
    return cache(f, new IdentityHashMap<>());
}
Stream-stringStream=stringList.Stream();
管柱最冷=stringStream.reduce((a,b)->
冷却指数(a)>冷却指数(b)?a:b;
).get()
只需首先创建(对象、度量)对:

公共静态可选最大化版本(列表ts,函数f){
返回ts.stream().map(t->Pair.Pair(t,f.apply(t)))
.max((p1,p2)->Integer.compare(p1.second(),p2.second())
.map(对::第一);
}

(这些是com.googlecode.totalylazy.Pair的)

如果速度/开销很重要,你不想使用Stream.max(Comparator),它会多次重新计算获胜对象的分数;上面的缓存解决方案可以工作,但会带来大量的O(N)开销。装饰器模式需要更多的内存分配/GC开销
String coolestString = stringList
        .stream()
        .max(Comparator.comparing(cache(CoolUtil::coolnessIndex)))
        .orElse(null);
Stream<String> stringStream = stringList.stream();
String coolest = stringStream.reduce((a,b)-> 
    coolnessIndex(a) > coolnessIndex(b) ? a:b;
).get()
/** 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;
}