web-dev-qa-db-ja.com

C#で辞書の最高値のキーを取得する良い方法

Dictionary<string, double> resultsの最大値のキーを取得しようとしています。

これは私がこれまでに持っているものです:

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

ただし、これは少し非効率的であるため、これを行うためのより良い方法があるかどうか疑問に思っていました。

67
Arda Xi

これは、最も読みやすいO(n)標準LINQを使用した回答です。

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

編集: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;
124
dss539

さまざまな提案を読んだ後、ベンチマークを行い、結果を共有することにしました。

テストしたコード:

// 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がコンパクトで柔軟な場合。

35
WhoIsRich

たぶん、これはLINQの良い使い方ではありません。 LINQソリューションを使用した辞書の2回の完全スキャンが表示されます(1回は最大値を取得し、もう1回はkvpを検索して文字列を返します)。

「昔ながらの」foreachを使用して1パスで実行できます。


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

これは高速な方法です。 O(n)が最適です。私が見る唯一の問題は、辞書を1回だけではなく2回繰り返すことです。

MaxBy from morelinq を使用して、辞書を1回反復処理できます。

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

OrderBy(最小値の検索用)またはOrderByDescending(最大値の場合)を使用して辞書をソートし、最初の要素を取得できます。また、2番目の最大/最小要素を見つける必要がある場合にも役立ちます

最大値で辞書キーを取得:

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

最小値で辞書キーを取得:

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

2番目の最大値で辞書キーを取得します。

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

2番目の最小値で辞書キーを取得します。

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

少しの拡張方法:

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

2
kamyker

スレッドセーフのために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)); 
    }
});

編集(上記の競合状態を回避するためにコードを更新):

並列に最小値を選択することを示す、より堅牢なパターンを次に示します。これは、競合状態に関する以下のコメントで言及されている懸念に対処していると思います。

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

私のバージョンは、オプションの比較子を使用した現在の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");
    }
0
Zar Shardan