在 C# 中获取字典最高值的键的好方法

Posted

技术标签:

【中文标题】在 C# 中获取字典最高值的键的好方法【英文标题】:Good way to get the key of the highest value of a Dictionary in C# 【发布时间】:2011-02-17 20:11:33 【问题描述】:

我正在尝试获取Dictionary<string, double> results中最大值的键。

这是我目前所拥有的:

double max = results.Max(kvp => kvp.Value);
return results.Where(kvp => kvp.Value == max).Select(kvp => kvp.Key).First();

但是,由于这似乎有点低效,我想知道是否有更好的方法来做到这一点。

【问题讨论】:

你的字典应该是 还是倒过来? 你是对的,它是 。已更正。 为什么在where后面有一个.Select?我对 LINQ 不太了解,只是好奇 @CoffeeAddict .Select 允许他进行“投影”在这里,他将 KeyValuePair 转换为一个 Key。他本可以省略这部分,直接写信.First().Key; 来获取密钥。 @dss539 啊,有点晚了,但你是对的。这样会更有效率。 【参考方案1】:

我认为这是使用标准 LINQ 时最易读的 O(n) 答案。

var max = results.Aggregate((l, r) => l.Value > r.Value ? l : r).Key;

编辑:CoffeeAddict 的解释

Aggregate是众所周知的函数概念Fold的LINQ名称

它遍历集合的每个元素并应用您提供的任何功能。 在这里,我提供的函数是一个返回较大值的比较函数。 循环时,Aggregate 会记住上次调用我的函数时的返回结果。它将它作为变量l 输入到我的比较函数中。变量r 是当前选中的元素。

因此,在聚合遍历整个集合之后,它会返回上次调用我的比较函数时的结果。然后我从中读取了.Key 成员,因为我知道这是一个字典条目

这是一种不同的方式来看待它[我不保证这会编译;)]

var l = results[0];
for(int i=1; i<results.Count(); ++i)

    var r = results[i];
    if(r.Value > l.Value)
        l = r;        

var max = l.Key;

【讨论】:

+1 dss539:我脑子里痒痒的,好像应该有办法用 LINQ 来做。不错!【参考方案2】:

在阅读了各种建议后,我决定对它们进行基准测试并分享结果。

测试的代码:

// TEST 1

for (int i = 0; i < 999999; i++)

  KeyValuePair<GameMove, int> bestMove1 = possibleMoves.First();
  foreach (KeyValuePair<GameMove, int> move in possibleMoves)
  
    if (move.Value > bestMove1.Value) bestMove1 = move;
  


// TEST 2

for (int i = 0; i < 999999; i++)

  KeyValuePair<GameMove, int> bestMove2 = possibleMoves.Aggregate((a, b) => a.Value > b.Value ? a : b);


// TEST 3

for (int i = 0; i < 999999; i++)

  KeyValuePair<GameMove, int> bestMove3 = (from move in possibleMoves orderby move.Value descending select move).First();


// TEST 4

for (int i = 0; i < 999999; i++)

  KeyValuePair<GameMove, int> bestMove4 = possibleMoves.OrderByDescending(entry => entry.Value).First();

结果:

Average Seconds Test 1 = 2.6
Average Seconds Test 2 = 4.4
Average Seconds Test 3 = 11.2
Average Seconds Test 4 = 11.2

这只是为了说明它们的相对性能。

如果您的优化“foreach”最快,但 LINQ 紧凑且灵活。

【讨论】:

+1 花时间替补。测试 3 和 4 有何不同?它们生成相同的 MSIL 不是吗? 我刚刚检查过,你是对的,测试 3 和 4 产生相同的 MSIL 代码 :)【参考方案3】:

也许这不是 LINQ 的好用处。我看到使用 LINQ 解决方案对字典进行了 2 次完整扫描(1 次获取最大值,然后另一次查找 kvp 以返回字符串。

您可以使用“老式”foreach 一次性完成:

KeyValuePair<string, double> max = new KeyValuePair<string, double>(); foreach (var kvp in results) if (kvp.Value > max.Value) max = kvp; return max.Key;

【讨论】:

我知道它会导致相同的结果,但我发现这更具可读性:var max = default(KeyValuePair&lt;string, double&gt;); 你是对的; OP 有一个使用 see 的 O(2n) 算法。请参阅我使用 LINQ 对 O(n) 的回答。 +1 用于减少迭代。您可能应该使用 double.MinValue 初始化 max.Value 以确保即使它是负数也能找到最大值。 保持简单 :) 经典搜索算法始终可用。没必要认为这会很困难。【参考方案4】:

您可以使用 OrderBy(查找最小值)或 OrderByDescending(查找最大值)对字典进行排序,然后获取第一个元素。当您需要找到第二个最大/最小元素时,它也会有所帮助

通过最大值获取字典键:

double min = results.OrderByDescending(x => x.Value).First().Key;

通过最小值获取字典键:

double min = results.OrderBy(x => x.Value).First().Key;

通过第二个最大值获取字典键:

double min = results.OrderByDescending(x => x.Value).Skip(1).First().Key;

通过第二个最小值获取字典键:

double min = results.OrderBy(x => x.Value).Skip(1).First().Key;

【讨论】:

看来OrderBy 的计算量比实际需要的要多。 是的。排序是 O(n * log (n)),最小/最大元素是 O(n)。【参考方案5】:

这是一种快速的方法。它是 O(n),这是最优的。我看到的唯一问题是它迭代字典两次而不是一次。

您可以使用来自morelinq 的MaxBy 对字典进行一次迭代。

results.MaxBy(kvp => kvp.Value).Key;

【讨论】:

Aggregate也可以达到同样的效果。【参考方案6】:

小扩展方法:

public static KeyValuePair<K, V> GetMaxValuePair<K,V>(this Dictionary<K, V> source)
    where V : IComparable

    KeyValuePair<K, V> maxPair = source.First();
    foreach (KeyValuePair<K, V> pair in source)
    
        if (pair.Value.CompareTo(maxPair.Value) > 0)
            maxPair = pair;
    
    return maxPair;

然后:

int keyOfMax = myDictionary.GetMaxValuePair().Key;

【讨论】:

【参考方案7】:

看看这些:

result.Where(x=>x.Value==result.Values.Max()).Select(x=>x.Key).ToList()

【讨论】:

【参考方案8】:

为了线程安全,使用 Interlocked.Exchange 并行执行如何 :) 请记住,Interlocked.Exchange 仅适用于引用类型。(即结构或键值对(除非包装在类中)不会努力保持最大值。

这是我自己的代码中的一个示例:

//Parallel O(n) solution for finding max kvp in a dictionary...
ClassificationResult maxValue = new ClassificationResult(-1,-1,double.MinValue);
Parallel.ForEach(pTotals, pTotal =>

    if(pTotal.Value > maxValue.score)
    
        Interlocked.Exchange(ref maxValue, new                
            ClassificationResult(mhSet.sequenceId,pTotal.Key,pTotal.Value)); 
    
);

编辑(更新代码以避免上述可能的竞争条件):

这是一个更强大的模式,它还显示了并行选择最小值。我认为这解决了以下 cmets 中提到的关于可能的竞争条件的问题:

int minVal = int.MaxValue;
Parallel.ForEach(dictionary.Values, curVal =>

  int oldVal = Volatile.Read(ref minVal);
  //val can equal anything but the oldVal
  int val = ~oldVal;

  //Keep trying the atomic update until we are sure that either:
  //1. CompareExchange successfully changed the value.
  //2. Another thread has updated minVal with a smaller number than curVal.
  //   (in the case of #2, the update is no longer needed)
  while (oldval > curVal && oldval != val)
  
    val = oldval;
    oldval = Interlocked.CompareExchange(ref minVal, curVal, oldval);
  
);

【讨论】:

我很确定这个例子有竞争条件。在您将最大值与当前值进行比较并交换它们之间,另一个线程可能已经做了同样的事情并且已经将一个更好的值交换到 maxValue 中,然后将被当前线程的更差值破坏。 我已经用更强大的解决方案更新了答案,我认为它可以解决潜在的竞争条件。 我认为你是对的。这就是我想解决比赛的方式。我确实想知道读写锁是否会具有更好的性能。 +1 更新【参考方案9】:

我的版本基于当前的 Enumerable.Max 实现,带有一个可选的比较器:

    public static TSource MaxValue<TSource, TConversionResult>(this IEnumerable<TSource> source, Func<TSource, TConversionResult> function, IComparer<TConversionResult> comparer = null)
    
        comparer = comparer ?? Comparer<TConversionResult>.Default;
        if (source == null) throw new ArgumentNullException(nameof(source));

        TSource max = default;
        TConversionResult maxFx = default;
        if ( (object)maxFx == null) //nullable stuff
        
            foreach (var x in source)
            
                var fx = function(x);
                if (fx == null || (maxFx != null && comparer.Compare(fx, maxFx) <= 0)) continue;
                maxFx = fx;
                max = x;
            
            return max;
        

        //valuetypes
        var notFirst = false;
        foreach (var x in source) 
        
            var fx = function(x);
            if (notFirst)
            
                if (comparer.Compare(fx, maxFx) <= 0) continue;
                maxFx = fx;
                max = x;
            
            else
            
                maxFx = fx;
                max = x;
                notFirst = true;
            
        
        if (notFirst)
            return max;
        throw new InvalidOperationException("Sequence contains no elements");
    

示例用法:

    class Wrapper
    
        public int Value  get; set;     
    

    [TestMethod]
    public void TestMaxValue()
    
        var dictionary = new Dictionary<string, Wrapper>();
        for (var i = 0; i < 19; i++)
        
            dictionary[$"s:i"] = new WrapperValue = (i % 10) * 10  ;
        

        var m = dictionary.Keys.MaxValue(x => dictionary[x].Value);
        Assert.AreEqual(m, "s:9");
    

【讨论】:

【参考方案10】:

我认为使用标准的 LINQ 库可以做到这一点。

【讨论】:

以上是关于在 C# 中获取字典最高值的键的好方法的主要内容,如果未能解决你的问题,请参考以下文章

Java中的Hashtable如何根据值获取键?

获取字典最小值的键,而该键在数组中

字典列表中所有键的联合

Python获取列表中键的键值

按字母顺序获取地图中的键的简单方法

在包含“无”/“假”值的字典上获取具有最小值的键