web-dev-qa-db-fra.com

Bon moyen d'obtenir la clé de la plus haute valeur d'un dictionnaire en C #

J'essaie d'obtenir la clé de la valeur maximale dans le Dictionary<string, double> results.

C'est ce que j'ai jusqu'ici:

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

Cependant, comme cela semble un peu inefficace, je me demandais s'il existait un meilleur moyen de le faire.

63
Arda Xi

Je pense que c'est la réponse la plus lisible O(n) en utilisant le standard LINQ.

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

edit: explication pour CoffeeAddict

Aggregate est le nom LINQ du concept fonctionnel généralement connu Fold

Il boucle sur chaque élément du jeu et applique la fonction que vous fournissez . Ici, la fonction que je fournis est une fonction de comparaison qui renvoie la plus grande valeur . En boucle, Aggregate mémorise le résultat renvoyé de la dernière fois. appelé ma fonction. Cela alimente ma fonction de comparaison en tant que variable l. La variable r est l'élément actuellement sélectionné.

Ainsi, une fois que l'agrégat a bouclé sur l'ensemble du jeu, il renvoie le résultat de la dernière fois où il a appelé ma fonction de comparaison. Ensuite, j'ai lu le membre .Key de celui-ci parce que je sais que c'est une entrée de dictionnaire

Voici une autre façon de voir les choses [Je ne garantis pas que cela 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;
115
dss539

Après avoir lu diverses suggestions, j'ai décidé de les comparer et de partager les résultats.

Le code testé:

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

Les resultats:

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

Ceci est juste pour donner une idée de leur performance relative.

Si votre optimisation 'foreach' est la plus rapide, mais LINQ est compact et flexible.

35
WhoIsRich

Peut-être que ce n'est pas une bonne utilisation de LINQ. Je vois 2 analyses complètes du dictionnaire en utilisant la solution LINQ (1 pour obtenir le maximum, puis une autre pour trouver le kvp pour retourner la chaîne.

Vous pouvez le faire en 1 seul passage avec un for old "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
JMarsch

C'est une méthode rapide. C'est O (n), ce qui est optimal. Le seul problème que je vois, c'est qu'il parcourt le dictionnaire deux fois au lieu d'une seule fois.

Vous pouvez le faire en parcourant le dictionnaire une fois en utilisant MaxBy from morelinq .

results.MaxBy(kvp => kvp.Value).Key;
6
Mark Byers

Vous pouvez trier le dictionnaire en utilisant OrderBy (pour trouver la valeur minimale) ou OrderByDescending (pour la valeur maximale) puis obtenir le premier élément. Cela aide aussi quand vous avez besoin de trouver le deuxième élément max/min

Obtenir la clé du dictionnaire par la valeur maximale:

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

Obtenir la clé du dictionnaire par la valeur minimale:

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

Obtenir la clé du dictionnaire par la deuxième valeur maximale:

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

Obtenir la clé du dictionnaire par la deuxième valeur minimale:

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

Pourquoi ne pas le faire en parallèle en utilisant Interlocked.Exchange pour la sécurité des threads :) Gardez à l’esprit que Interlocked.Exchange fonctionnera uniquement avec un type de référence (c’est-à-dire une paire clé/structure (sauf si elle est encapsulée dans une classe) ne fonctionnera pas. la valeur max. 

Voici un exemple de mon propre code: 

//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 (Code mis à jour pour éviter une éventuelle condition de concurrence plus haut):

Voici un modèle plus robuste qui montre également la sélection d'une valeur min en parallèle. Je pense que cela répond aux préoccupations mentionnées dans les commentaires ci-dessous concernant une condition de concurrence critique: 

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
Jake Drew

Ma version est basée sur l'implémentation actuelle Enumerable.Max avec un comparateur facultatif:

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

Exemple d'utilisation:

    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");
    }
0
Zar Shardan