【问题标题】:Maximum of list with expensive custom key function具有昂贵的自定义键功能的列表的最大值
【发布时间】:2018-09-26 12:32:11
【问题描述】:

在 Java 中查找您编写的序列的最大元素:

GameState bestGs = Collections.max(ns,
        Comparator.comparing(e -> minimax(e)));

这里minimax 是一个返回数字的函数,ns 是一个集合。该代码有效,但对于集合的每个元素,将多次评估 key 函数。我如何制作它以便每个元素只评估一次?在 Python 中你只需写 max(seq, key = lambda e: minimax(e))Java 中一定有类似的东西吗?别叫我自己写forloop,21世纪我不该写!

显式循环代码如下:

GameState best = null;
// Doesn't matter what scalar type is used.
int bestScore = Integer.MIN_VALUE;  
for (GameState n : ns) {
    int thisScore = minimax(n);
    if (thisScore > bestScore) {
        bestScore = thisScore;
        best = n;
    }
}

我想在 Java 中以“函数式”的方式编写上述内容,同时保持高性能。

【问题讨论】:

  • minimax() 的结果可以用 e 作为键存储在封闭类中的 Map 中。首先检查地图,看看是否已经计算了值。

标签: java collections java-8 max comparator


【解决方案1】:

你可以memoizee -> minimax(e)函数:

public static <T, S> Function<T, S> memoize(Function<T, S> function) {
    Map<T, S> cache = new HashMap<>();
    return argument -> cache.computeIfAbsent(argument, function);
}

然后,只需使用 memoized 函数:

GameState bestGs = Collections.max(ns,
    Comparator.comparing(memoize(e -> minimax(e))));

编辑:这种方法要求GameState 实现hashCodeequals一致。这些方法也应该运行得非常快(这是通常的情况)。


编辑 2: 正如 M. Justin 在下面的 cmets 中所说,此解决方案不是线程安全的。如果你要在多个线程中使用 memoized 函数,你应该使用 ConcurrentHashMap 而不是 HashMap

【讨论】:

  • 不幸的是,上述解决方案中的性能损失非常大。
  • @FedericoPeraltaSchaffner 不知道你是否意识到,但另一个美妙之处在于 minimax 不是针对重复键计算的
  • 请注意memoize 的这种实现不是线程安全的。如果您曾经从Collections.max 切换到并行的max 实现,则需要从HashMap 切换到允许并发访问的东西,例如ConcurrentHashMap
  • @M.Justin 关于您的第一条评论,我认为这取决于minimax 在时间方面的成本。从地图返回Integer 总是比调用昂贵的方法快得多。关于您的第二条评论,您是绝对正确的,我将添加一个编辑。感谢您的反馈!
  • @fps — 我更多地解决了“手写循环仅缓存运行最大值,但您的代码为序列中的每个 GameState 存储一个分数”带来的空间使用问题。比时间问题。但是你提出了一个有价值的观点。无论哪种方式,这都不太可能对现实系统产生显着影响,除非在试图避免创建对象的实际实时 Java 环境中运行。
【解决方案2】:
import java.util.ArrayList;
import java.util.List;

import static java.lang.Integer.MIN_VALUE;
import static java.util.AbstractMap.SimpleEntry;

...

var bestEntry = ns.stream()
                  .map(i -> new SimpleEntry<>(i, minimax(i)))
                  .max(Map.Entry.comparingByValue())
                  .orElse(new SimpleEntry<>(null, MIN_VALUE));

var bestGameState = bestEntry.getKey();
var bestScore = bestEntry.getValue();

减少后,你会得到一个Optional&lt;Pair&lt;GameState, Integer&gt;&gt;,其中可能包含最高的minimax结果和一个对应的GameState。如果没有游戏状态,我们返回默认条目new SimpleEntry&lt;&gt;(null, MIN_VALUE)

【讨论】:

  • 感谢您的反对,但请解释一下:)
  • 我没有投反对票。您的解决方案符合我的标准,但如果它是 Java 中的最佳解决方案,我会感到惊讶。
  • @BjörnLindqvist 我能想到的最好的……你为什么不喜欢它?
  • @BjörnLindqvist 我认为你需要在你的总结中做一个小修正:“聪明的人很好。如果世界上有更多的精英管理,世界会变得更美好。”
  • 可以用.reduce((a, b) -&gt; a.getValue() &gt;= b.getValue() ? a : b)代替.max(Map.Entry.comparingByValue())
【解决方案3】:
GameState max = ns.stream()
                 .collect(Collectors.toMap(str -> minimax(str), Function.identity(), (gs1, gs2) -> gs1,
                         (Supplier<Map<Integer, GameState>>)() -> new TreeMap<>(Comparator.reverseOrder())
                 )).values().iterator().next();

【讨论】:

  • next() 没有hasNext()?
  • 嗯,是的。只有当集合为空时才可能。之前应该检查一下。
  • @oleg.cherednik Supplier 是不需要的,推理可以正常工作(除非你使用有问题的 Eclipse);比你不需要reverseOrder - 只需获取最后一个条目;并且由于您确实使用了TreeMap,因此可能会利用它的方法,例如lastXXXfirstXXX;这可以使用collectingAndThen 来完成,以使其成为单个管道
【解决方案4】:

好吧,首先为每个键计算 minimax,然后将其存储在 TreeMap 中,然后在计算后获取最后一个条目,怎么样?当然,您要为额外的存储空间付费。

int max = col.stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.toMap(
                            e -> minimax(e),
                            Function.identity(),
                            (left, right) -> left,
                            TreeMap::new
                    ),
                    m -> m.lastEntry().getValue()));

这可以做得更简洁:

// Integer, Integer for simplicity
TreeMap<Integer, Integer> tm = new TreeMap<>();
col.forEach(x-> { 
            tm.putIfAbsent(minimax(x), x); 
            if(tm.size() > 1) {
                tm.remove(tm.firstKey()); 
            }
});
int max = m.lastEntry().getValue();

【讨论】:

  • @FedericoPeraltaSchaffner 哦,该死的,对。但是知道我看了你的解决方案,这很有意义,当我发布这个时是下午 12 点(蹩脚的借口,我知道)......
猜你喜欢
  • 1970-01-01
  • 2022-08-06
  • 1970-01-01
  • 2019-12-10
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多