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


77

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

这是我到目前为止所拥有的:

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

但是,由于这似乎效率不高,所以我想知道是否有更好的方法可以做到这一点。


2
您的字典应该是<double,string>还是倒数?
斯蒂芬

1
您是对的,它是<string,double>。已更正。
阿尔达(Arda Xi)2010年

为什么在后面有一个.Select?我对LINQ不太了解,只是感到好奇
PositiveGuy

1
@CoffeeAddict .Select允许他进行“投影”在这里,他将KeyValuePair转换为一个Key。他本可以省去这部分,而只是写.First().Key;来获取密钥。
dss539 2013年

@ dss539啊,有点晚了,但是你是对的。这样会更有效率。
阿尔达(Arda Xi)

Answers:


135

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

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

编辑:CoffeeAddict的说明

Aggregate是常用功能概念Fold的LINQ名称

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

因此,在aggregate遍历整个集合之后,它将返回上一次调用比较函数的结果。然后我.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;

3
+1 dss539:我的大脑发痒了,就像应该使用LINQ进行某种处理一样。真好!
JMarsch

40

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

经过测试的代码:

// 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紧凑而灵活。


5
+1以抽出时间进行测试。测试3和4有何不同?它们生成相同的MSIL,不是吗?
dss539 2011年

1
我刚刚检查了一下,您是正确的,测试3和4产生相同的MSIL代码:)
WhoIsRich 2011年

11

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

您可以使用“老式” foreach进行1遍操作:


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


1
我知道它会导致相同的结果,但我发现它更具可读性:var max = default(KeyValuePair<string, double>);
ChaosPandion 2010年

2
你是对的; OP使用see的O(2n)算法。请参阅我关于使用LINQ的O(n)的答案。
dss539 2010年

4
+1可减少迭代次数。您可能应该使用double.MinValue初始化max.Value以确保找到最大值,即使它是负数也是如此。
克里斯·泰勒

保持简单:)经典搜索算法始终可用。无需考虑会很难。
DavutGürbüz2012年

7

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

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

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

Aggregate也可以用于相同的效果。
dss539 2010年

5

您可以使用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;

1
似乎OrderBy执行了比实际需要更多的计算。
Babak.Abad

是。排序为O(n * log(n)),最小/最大元素为O(n)。
Zar Shardan

3

小扩展方法:

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;


2

检查这些:

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


0

为了确保线程安全,如何使用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)); 
    }
});

编辑(更新的代码,以避免上面可能出现的争用情况):

这是一个更健壮的模式,它还显示了并行选择最小值。我认为这解决了以下评论中提到的有关可能的比赛条件的担忧:

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,然后该值将被当前线程的较差值所破坏。
dss539 2015年

我已经用更强大的解决方案更新了答案,我认为该解决方案可以解决潜在的比赛条件。
杰克·德鲁

我想你是正确的。这就是我考虑解决种族的方式。我确实想知道读写锁是否会具有更好的性能。+1更新
dss539,2015年

0

我的版本基于当前的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 Wrapper{Value = (i % 10) * 10 } ;
        }

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

-4

我认为使用标准LINQ库,这是您所能达到的最快速度。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.