Buena manera de obtener la clave del valor más alto de un diccionario en C#


Estoy tratando de obtener la clave del valor máximo en el Dictionary<string, double> results.

Esto es lo que tengo hasta ahora:

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

Sin embargo, dado que esto parece un poco ineficiente, me preguntaba si había una mejor manera de hacer esto.

Author: Arda Xi, 2010-05-10

8 answers

Creo que esta es la respuesta O(n) más legible usando LINQ estándar.

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

Editar: explicación para CoffeeAddict

Aggregate es el nombre LINQ para el concepto funcional comúnmente conocido Fold

Gira sobre cada elemento del conjunto y aplica cualquier función que usted proporcione. Aquí, la función que proporciono es una función de comparación que devuelve el valor más grande. Durante el bucle, Aggregate recuerda el resultado de retorno de la última vez que llamó a mi función. Se alimenta esto en mi función de comparación como variable l. La variable r es el elemento seleccionado actualmente.

Así que después de que aggregate se ha extendido por todo el conjunto, devuelve el resultado de la última vez que llamó a mi función de comparación. Entonces leí el .Key miembro de él porque sé que es una entrada de diccionario

Aquí hay una forma diferente de verlo [No garantizo que esto compile ;)]

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;
 107
Author: dss539,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-22 06:34:03

Después de leer varias sugerencias, decidí compararlas y compartir los resultados.

El código probado:

// 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();
}

Los resultados:

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

Esto es solo para dar una idea de su rendimiento relativo.

Si su optimización 'foreach' es más rápida, pero LINQ es compacta y flexible.

 35
Author: WhoIsRich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-01 19:48:42

Tal vez esto no es un buen uso para LINQ. Veo 2 escaneos completos del diccionario usando la solución LINQ (1 para obtener el máximo, luego otro para encontrar el kvp para devolver la cadena.

Podrías hacerlo en 1 pasada con un foreach "old fashioned":


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

 11
Author: JMarsch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 19:49:38

Este es un método rápido. Es O (n), que es óptimo. El único problema que veo es que itera sobre el diccionario dos veces en lugar de solo una vez.

Puedes hacerlo iterando sobre el diccionario una vez usando MaxBy de morelinq.

results.MaxBy(kvp => kvp.Value).Key;
 6
Author: Mark Byers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-27 19:33:39

Puede ordenar el diccionario usando OrderBy (para buscar el valor mínimo) o OrderByDescending (para el valor máximo) y luego obtener el primer elemento. También ayuda cuando necesita encontrar segundo elemento max / min

Obtener la clave del diccionario por valor máximo:

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

Obtener la clave del diccionario por valor mínimo:

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

Obtener la clave del diccionario por segundo valor máximo:

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

Obtener la clave del diccionario por segundo valor mínimo:

double min = results.OrderBy(x => x.Value).Skip(1).First().Key;
 1
Author: Geograph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-28 12:04:15

Qué tal hacerlo en paralelo usando Entrelazado.Cambio por la seguridad del hilo:) Tenga en cuenta que entrelazado.Exchange solo funcionará con un tipo de referencia.(es decir, un par de valor de estructura o clave (a menos que esté envuelto en una clase) no funcionará para mantener el valor máximo.

Aquí hay un ejemplo de mi propio código:

//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)); 
    }
});

EDITAR (Código actualizado para evitar una posible condición de carrera anterior):

Aquí hay un patrón más robusto que también muestra la selección de un valor mínimo en paralelo. Me creo que esto aborda las preocupaciones mencionadas en los comentarios a continuación con respecto a una posible condición de carrera:

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);
  }
});
 0
Author: Jake Drew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-27 19:49:54

Método de extensión pequeña:

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;
}

Entonces:

int keyOfMax = myDictionary.GetMaxValuePair().Key;

 0
Author: kamyker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-12 22:28:37

Creo que usar las bibliotecas LINQ estándar es lo más rápido que puedes hacer.

 -4
Author: PSU_Kardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-10 19:30:23