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