web-dev-qa-db-de.com

Gute Möglichkeit, den Schlüssel des höchsten Wertes eines Dictionary in C # zu erhalten

Ich versuche den Schlüssel des Maximalwerts im Dictionary<string, double> results zu erhalten.

Das habe ich bisher:

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

Da dies jedoch ineffizient erscheint, habe ich mich gefragt, ob es einen besseren Weg gibt, dies zu tun.

63
Arda Xi

Ich denke, das ist die am besten lesbare O(n) Antwort unter Verwendung von Standard LINQ.

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

edit: Erklärung für CoffeeAddict

Aggregate ist der LINQ-Name für das allgemein bekannte Funktionskonzept Fold

Es durchläuft jedes Element des Sets und wendet jede Funktion an, die Sie bereitstellen. Hier ist die Funktion, die ich zur Verfügung stellen, eine Vergleichsfunktion, die den größeren Wert zurückgibt. Während der Schleife wiederholt sich Aggregate das Ergebnis der letzten Änderung rief meine Funktion an. Es füttert dies als Variable l in meine Vergleichsfunktion. Die Variable r ist das aktuell ausgewählte Element.

Nachdem das Aggregat die gesamte Menge durchlaufen hat, gibt es das Ergebnis vom letzten Mal zurück, als es meine Vergleichsfunktion aufgerufen hat. Dann habe ich das .Key-Mitglied daraus gelesen, weil ich weiß, dass es sich um einen Wörterbucheintrag handelt

Hier ist eine andere Sichtweise [ich kann nicht garantieren, dass dies kompiliert wird;)]

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

Nachdem ich verschiedene Vorschläge gelesen hatte, beschloss ich, sie zu bewerten und die Ergebnisse zu teilen.

Der Code getestet:

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

Die Ergebnisse:

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

Dies soll nur eine Vorstellung von ihrer relativen Leistung geben.

Wenn Ihre Optimierung für "foreach" am schnellsten ist, ist LINQ kompakt und flexibel.

35
WhoIsRich

Vielleicht ist dies keine gute Verwendung für LINQ. Ich sehe zwei vollständige Scans des Wörterbuchs mit der LINQ-Lösung (1, um den Maximalwert zu erhalten, dann einen weiteren, um den Kvp zu finden, um den String zurückzugeben.

Sie könnten es in einem Durchgang mit einem "altmodischen" Foreach machen:


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

Dies ist eine schnelle Methode. Es ist O (n), was optimal ist. Das einzige Problem, das ich sehe, ist, dass das Wörterbuch zweimal statt nur einmal durchlaufen wird.

Sie können das Wörterbuch einmal durchlaufen, indem Sie MaxBy from morelinq verwenden.

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

Kleine Erweiterungsmethode:

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

Dann:

int keyOfMax = myDictionary.GetMaxValuePair().Key;

2
kamyker

Sie können das Wörterbuch sortieren, indem Sie OrderBy (für Mindestwert suchen) oder OrderByDescending (für Maximalwert) verwenden und dann das erste Element abrufen. Es hilft auch, wenn Sie ein zweites Max/Min-Element suchen

Wörterbuchschlüssel nach Maximalwert abrufen:

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

Wörterbuchschlüssel nach Mindestwert abrufen:

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

Erhalte den Wörterbuchschlüssel um den zweiten Maximalwert:

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

Holen Sie sich den Wörterbuchschlüssel in Sekundenschnelle:

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

Verwenden Sie Interlocked.Exchange für die Thread-Sicherheit parallel. :) Denken Sie daran, dass Interlocked.Exchange nur mit einem Referenztyp funktioniert (dh ein Struktur- oder Schlüsselwertpaar (sofern nicht in eine Klasse eingeschlossen) wird nicht funktionieren der maximale Wert. 

Hier ist ein Beispiel aus meinem eigenen 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)); 
    }
});

BEARBEITEN (aktualisierter Code, um mögliche Rennbedingungen oben zu vermeiden):

Hier ist ein robusteres Muster, das auch die parallele Auswahl eines Mindestwerts zeigt. Ich denke, dass dies die Bedenken betrifft, die in den folgenden Kommentaren bezüglich einer möglichen Rennbedingung erwähnt werden: 

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

Meine Version basiert auf der aktuellen Enumerable.Max-Implementierung mit einem optionalen Vergleich:

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

Beispielverwendung:

    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