C #에서 Dictionary의 가장 높은 값의 키를 얻는 좋은 방법
.NET Framework에서 최대 값의 키를 얻으려고합니다 Dictionary<string, double> results
.
이것이 내가 지금까지 가지고있는 것입니다.
double max = results.Max(kvp => kvp.Value);
return results.Where(kvp => kvp.Value == max).Select(kvp => kvp.Key).First();
그러나 이것은 약간 비효율적으로 보이므로 더 나은 방법이 있는지 궁금합니다.
나는 이것이 표준 LINQ를 사용하는 가장 읽기 쉬운 O (n) 대답이라고 생각합니다.
var max = results.Aggregate((l, r) => l.Value > r.Value ? l : r).Key;
편집 : CoffeeAddict에 대한 설명
Aggregate
일반적으로 알려진 기능 개념 Fold 의 LINQ 이름입니다.
세트의 각 요소를 반복하고 제공하는 모든 기능을 적용합니다. 여기서 제가 제공하는 함수는 더 큰 값을 반환하는 비교 함수입니다. 루핑하는 동안 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;
다양한 제안을 읽은 후 벤치마킹하고 결과를 공유하기로 결정했습니다.
테스트 된 코드 :
// 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는 작고 유연합니다.
아마도 이것은 LINQ에 적합하지 않을 수 있습니다. LINQ 솔루션을 사용하여 사전에 대한 2 개의 전체 스캔이 표시됩니다 (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;
이것은 빠른 방법입니다. 최적 인 O (n)입니다. 내가 보는 유일한 문제는 사전을 한 번이 아니라 두 번 반복한다는 것입니다.
morelinq의 MaxBy 를 사용하여 사전을 한 번 반복 할 수 있습니다 .
results.MaxBy(kvp => kvp.Value).Key;
OrderBy (최소값 찾기) 또는 OrderByDescending (최대 값)을 사용하여 사전을 정렬 한 다음 첫 번째 요소를 가져올 수 있습니다. 또한 두 번째 최대 / 최소 요소를 찾을 때 도움이됩니다.
최대 값으로 사전 키 가져 오기 :
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;
작은 확장 방법 :
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;
스레드 안전성을 위해 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);
}
});
선택적 비교자를 사용하여 현재 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");
}
I think using the standard LINQ Libraries this is as fast as you can go.
'Programing' 카테고리의 다른 글
Raspberry Pi의 화면 해상도를 변경하는 방법 (0) | 2020.11.07 |
---|---|
헤더 파일의 여러 클래스 vs. 클래스 당 단일 헤더 파일 (0) | 2020.11.07 |
Request.QueryString에 ASP.NET에서 특정 값이 있는지 확인하는 방법은 무엇입니까? (0) | 2020.11.07 |
삽입 정렬과 버블 정렬 알고리즘 (0) | 2020.11.07 |
모든 com.android.support 라이브러리는 정확히 동일한 버전을 사용해야합니다. (0) | 2020.11.07 |