Хороший спосіб отримати ключ найвищого значення словника на C #


78

Я намагаюся отримати ключ максимального значення в Dictionary<string, double> results.

Це те, що я маю дотепер:

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

Однак, оскільки це здається трохи неефективним, мені було цікаво, чи є кращий спосіб це зробити.


2
Ваш словник повинен бути <double, string> чи це назад?
Стефан

1
Ви маєте рацію, це <рядок, подвійний>. Виправлено.
Арда Сі

чому у вас є .Виберіть після де? Я не такий розсудливий з LINQ, просто цікаво
PositiveGuy

1
@CoffeeAddict .Select дозволяє йому робити "проекцію". Тут він перетворює KeyValuePair у лише ключ. Він міг залишити цю частину і просто написати, .First().Key;щоб отримати ключ.
dss539

@ dss539 Ах, трохи пізно, але ти маєш рацію. Це було б ефективніше.
Арда Сі,

Відповіді:


136

Я думаю, що це найбільш читабельна відповідь 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;

3
+1 dss539: У мене в голові свербіло, мовляв, мав бути якийсь спосіб зробити це за допомогою LINQ. Приємно!
JMarsch

41

Прочитавши різні пропозиції, я вирішив порівняти їх і поділитися результатами.

Тестований код:

// 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 компактний та гнучкий.


5
+1 за те, що ви витратили час на тестування. Чим відрізняються тест 3 і 4? Вони генерують однаковий MSIL, чи не так?
dss539

1
Я щойно перевірив, і ви маєте рацію, тест 3 і 4 видають той самий код MSIL :)
WhoIsRich

11

Можливо, це невдале використання для LINQ. Я бачу 2 повних сканування словника за допомогою рішення LINQ (1, щоб отримати максимум, потім ще одне, щоб знайти kvp для повернення рядка.

Ви можете зробити це за один прохід зі "старомодним" передбаченням:


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


1
Я знаю, що це призводить до того ж результату, але я виявив, що це є більш читабельним:var max = default(KeyValuePair<string, double>);
ChaosPandion

2
Ти правий; OP мав алгоритм O (2n), використовуючи див. Дивіться мою відповідь на O (n) за допомогою LINQ.
dss539

4
+1 для зменшення ітерацій. Ймовірно, слід ініціалізувати max.Value за допомогою double.MinValue, щоб переконатися, що ви знайдете max, навіть якщо це від’ємне значення.
Кріс Тейлор

нехай це буде просто :) класичні алгоритми пошуку завжди доступні. Не потрібно думати, що це буде важко.
Давут ürюрбюз

7

Це швидкий метод. Оптимальним є O (n). Єдина проблема, яку я бачу, полягає в тому, що він повторює словник двічі замість одного разу.

Ви можете зробити це ітерацію словника один раз, використовуючи MaxBy від morelinq .

results.MaxBy(kvp => kvp.Value).Key;

Aggregateтакож можна використовувати для того ж ефекту.
dss539

5

Ви можете сортувати словник за допомогою OrderBy (для знаходження мінімального значення) або OrderByDescending (для максимального значення), а потім отримати перший елемент. Це також допоможе, коли вам потрібно знайти другий елемент max / min

Отримати ключ словника за максимальним значенням:

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

Отримати ключ словника за мінімальним значенням:

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

Отримайте ключ словника за другим максимальним значенням:

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

Отримайте ключ словника за другим мінімальним значенням:

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

1
Здається, OrderByробити більше обчислень, ніж те, що насправді потрібно.
Бабак. Абад,

Так. Сортування - O (n * log (n)), а елемент min / max - O (n).
Зар

3

Метод невеликого розширення:

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;


3

Перевірте це:

result.Where (x => x.Value == result.Values.Max ()). Виберіть (x => x.Key) .ToList ()


0

Як щодо того, щоб робити це паралельно, використовуючи 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);
  }
});

Я майже впевнений, що цей приклад має перегоновий стан. Між тим, як ви порівнюєте максимальне значення з поточним і поміняєте їх місцями, інший потік міг би зробити те саме і вже поміняти краще значення на maxValue, яке потім буде заблоковано гіршим значенням поточного потоку.
dss539

Я оновив відповідь більш надійним рішенням, яке, на мою думку, стосується потенційних умов перегонів.
Джейк Дрю,

Гадаю, ти маєш рацію. Ось так я думав вирішити перегони. Мені цікаво, чи матиме блокування читання-запису кращу продуктивність. +1 за оновлення
dss539

0

Моя версія базується на поточній реалізації 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");
    }

-4

Я думаю, що користуючись стандартними бібліотеками LINQ, це можливо якомога швидше.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.