在C#中获取Dictionary最高值的关键的好方法

时间:2010-05-10 19:16:16

标签: c# linq dictionary max

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

这是我到目前为止所做的:

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

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

10 个答案:

答案 0 :(得分:121)

我认为这是使用标准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 :(得分:35)

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

测试代码:

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

答案 2 :(得分: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;

答案 3 :(得分:6)

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

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

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

答案 4 :(得分:3)

检查以下内容:

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

答案 5 :(得分:1)

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

时,它也会有所帮助

按最大值获取字典键:

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;

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

insert into t_test (kurs_id, datekurs)
with CTE (DD) as
(
select to_date('20170101','YYYYMMDD') as DD
from dual
union
select DD +1
from CTE
where DD < to_date('20171231','YYYYMMDD')
)
select  row_number() over(order by DD) as kurs_id, DD as datekurs
from CTE

答案 6 :(得分:1)

小扩展方法:

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

答案 8 :(得分: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");
    }

答案 9 :(得分:-4)

我认为使用标准LINQ库可以尽可能快地使用它。