2016-04-21 2739 views
14

我有一个对象流,我想找到一个具有某个属性的最大值的元素,它的计算开销很大。Java Stream:找到具有属性的最小值/最大值的元素

作为一个特定的简单例子,假设我们有一个字符串列表,并且我们想要找到最酷的一个,给定一个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); 

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

+2

相关但不完全重复:http://stackoverflow.com/questions/27606185/arg-max-in-java-8-streams(其中关注是代码简洁而不是效率,我认为推荐的解决方案仍然最终会反复调用相同的'coolnessIndex')。 –

+0

我不相信Java流API中有任何与此相当的东西。你可以实现你自己的'maxByAttribute'版本(别人已经完成了某些[这里](https://gist.github.com/mapio/57299694ef94cc88dddb),但我没有看过他们的代码),或者你可以使用'map'来获得一串对('s','coolnessIndex(s)'),然后是'max' - 但是AIUI Java没有一个方便的pair类,所以你最终会得到一个很多样板代码,更不用说所有额外的内存分配。 –

+0

您可以通过字符串 - >凉爽结果进行分组,然后选择最酷的字符串。请参阅https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function- –

回答

1

感谢大家建议的变体。最后,我发现我最喜欢的在Efficiency of the way comparator works的解决方案 - 从bayou.io答案:

有一个通用的方法cache

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

这可能被用来作为如下:

String coolestString = stringList 
     .stream() 
     .max(Comparator.comparing(cache(CoolUtil::coolnessIndex))) 
     .orElse(null); 
1

如何使用两个流,一个创造与预先计算的值和第二使用地图项设置为找到最高值的地图:

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

或者你可以使用'TreeMap'来按照coolness index排序的方式收集条目 - 这可以帮助你摆脱另一个流。 –

0

这是一个减少的问题。将列表降至特定值。总的来说,减少工作在部分解决方案和列表中的项目上。在这种情况下,这意味着将之前的“获胜”值与列表中的新值进行比较,这将在每次比较中计算两次昂贵的操作。

根据https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html另一种方法是使用collect来代替reduce。

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

class Cooler implements Consumer<String>{ 

    String coolestString = ""; 
    int coolestValue = 0; 

    public String coolest(){ 
     return coolestString; 
    } 
    @Override 
    public void accept(String arg0) { 
     combine(arg0, expensive(arg0)); 
    } 

    private void combine (String other, int exp){ 
     if (coolestValue < exp){ 
      coolestString = other; 
      coolestValue = exp; 
     } 
    } 
    public void combine(Cooler other){ 
     combine(other.coolestString, other.coolestValue); 
    } 
} 

该类接受一个字符串,如果它是比以前的赢家冷却器,它取代了它,并节省了昂贵的计算值。

Cooler cooler = Stream.of("java", "php", "clojure", "c", "lisp") 
       .collect(Cooler::new, Cooler::accept, Cooler::combine); 
System.out.println(cooler.coolest()); 
0

我会创造一个局部类(一个内部定义一个类的方法,罕见的,但完全合法的),和你的对象映射到这一点,所以昂贵的属性,准确计算每进行一次:

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

您可以利用的想法从流中收集适当的结果。昂贵的冷静计算函数的约束使您可以考虑为流的每个元素精确调用一次该函数。

Java 8在Stream上提供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; 
    } 
} 

此代码打印stooge最大的冷静和coolness方法正好叫每个stooge一次。作为mergeFunction(a, b) ->a)的BinaryOperator可以进一步改进。

7

下面是使用Object[]作为一个元组,不是最漂亮的代码,但简洁

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); 
5
Stream<String> stringStream = stringList.stream(); 
String coolest = stringStream.reduce((a,b)-> 
    coolnessIndex(a) > coolnessIndex(b) ? a:b; 
).get() 
0

刚刚创建(对象,公制)对第一:

public static <T> Optional<T> maximizeOver(List<T> ts, Function<T,Integer> f) { 
    return ts.stream().map(t -> Pair.pair(t, f.apply(t))) 
     .max((p1,p2) -> Integer.compare(p1.second(), p2.second())) 
     .map(Pair::first); 
} 

(这些都是com.googlecode.totallylazy.Pair的)

相关问题