Хороший способ получить ключ от наивысшего значения словаря в С#

Я пытаюсь получить ключ максимального значения в Dictionary<string, double> results.

Это то, что у меня есть до сих пор:

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

Однако, поскольку это кажется немного неэффективным, мне было интересно, есть ли лучший способ сделать это.

Ответ 1

Я думаю, что это самый читаемый ответ O (n) с использованием стандартного LINQ.

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

edit: объяснение для CoffeeAddict

Aggregate - это имя LINQ для общеизвестной функциональной концепции Fold

Он перемещается по каждому элементу набора и применяет любую функцию, которую вы предоставляете. Здесь функция, которую я предоставляю, представляет собой функцию сравнения, которая возвращает большее значение. Во время цикла 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;

Ответ 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 является компактным и гибким.

Ответ 3

Возможно, это не очень полезно для LINQ. Я вижу 2 полных сканирования словаря с помощью решения LINQ (1, чтобы получить max, затем еще один, чтобы найти kvp для возврата строки.

Вы можете сделать это за 1 проход с "старомодным" foreach:


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

Ответ 4

Это быстрый метод. Это O (n), что является оптимальным. Единственная проблема, которую я вижу, заключается в том, что она выполняет итерацию по словарю дважды, а не только один раз.

Вы можете сделать это, итерации по словарю один раз, используя MaxBy из morelinq.

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

Ответ 5

Вы можете сортировать словарь, используя OrderBy (для определения значения min) или 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;

Получить ключ словаря вторым значением min:

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

Ответ 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

Как сделать это параллельно с помощью 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)); 
    }
});

EDIT (обновленный код, чтобы избежать возможного состояния гонки выше):

Здесь показан более прочный шаблон, который также показывает выбор минимального значения параллельно. Я думаю, что это касается проблем, упомянутых в комментариях ниже относительно возможного состояния гонки:

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

Моя версия основана на текущей реализации 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

Я думаю, что с помощью стандартных библиотек LINQ это так же быстро, как вы можете.