【问题标题】:What's the best way to get top nth values from Dictionary?从字典中获取前 n 个值的最佳方法是什么?
【发布时间】:2021-10-14 23:31:56
【问题描述】:

我正在尝试从字典中获取前 n 个值。让我不容易的部分是,如果有多个相同等级的值,我需要保留所有这些值。 例如,如果字典看起来像:

Dictionary<string, int> dict = new Dictionary<string, int>();
dict.Add("AAA", 91);
dict.Add("BBB", 97);
dict.Add("CCC", 98);
dict.Add("DDD", 92);
dict.Add("EEE", 97);
dict.Add("FFF", 100);

如果我想要前 3 名,我需要获得

dict.Add("BBB", 97);
dict.Add("CCC", 98);
dict.Add("EEE", 97);
dict.Add("FFF", 100);

因为 BBB 和 EEE 具有相同的等级。我首先按排名对字典进行排序,并尝试了Take(),但只取了两者之一。

/* Does not work */
var dictSorted = dict.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value).Take(3);

/* The below only prints 
    FFF = 100
    CCC = 98
    BBB = 97

    but not  
    EEE = 97
*/
foreach(KeyValuePair kvp in dictSorted){
    Console.WriteLine(kvp.Key + " = " + kvp.Value);
}

有没有什么好的方法可以做到这一点?

[编辑] 抱歉我的问题不清楚。我想说的是, 我需要保持前 3 个不同的排名。如果有并列,则都需要包括在内,但仍处于同一排名内。

例如,如果候选人是:

100、100、100、99、99、98、98、97、97、96,

那么,我需要 100、100、100、99、99、98、98,因为这些是前 3 个不同排名。我正在使用字典,因为每个等级都与 AAA、BBB、CCC 等名称相关联。 从技术上讲,在上面的示例中,top 3 通常意味着 100、100、100 仅此而已,但就我而言,它需要是前 3 个“不同”排名。

【问题讨论】:

  • .OrderByDescending(x =&gt; x.Value).Take(3)?
  • 我想我不需要那行代码的后半部分。
  • @JeremyLakeman 我删除了“ToDictionary....”并且代码产生了相同的结果,所以我删除了它。但它仍然没有保持相同排名的条目。它只打印 3 个条目。
  • 编写你自己的.Take 方法来不断检查后续元素是否相等。
  • 听起来您使用的数据结构完全错误。您可能想要SortedList 或类似名称。字典是一种哈希表,没有保证的顺序

标签: c# dictionary


【解决方案1】:

您可以使用 groupingGroupBy 方法将具有不同 Key 但相同 Values 的项目包含到结果中:

var kvpGroups = dict.OrderByDescending(x => x.Value).GroupBy(x => x.Value).Take(3);

这里你取前 3 个KeyValuePairs,你取前 3 个 KeyValuePairs。如果每个组的Value 在源集合中是唯一的,则每个组可能包含一个KeyValuePair;如果其中一些具有相同的Values,则可能包含多个KeyValuePairs。

您可以使用foreach 以下列方式迭代组(kvp 表示KeyValuePair):

foreach (var kvpGroup in kvpGroups)
{
    foreach (var kvp in kvpGroup)
    {
        Console.WriteLine(kvp.Key + " = " + kvp.Value);
    }              
}

// Output:
// FFF = 100
// CCC = 98
// BBB = 97
// EEE = 97

或 LINQ 版本:

foreach (var kvp in from kvpGroup in (from kvp in dict
                                      orderby kvp.Value descending
                                      group kvp by kvp.Value into kvpGroup
                                      select kvpGroup).Take(3)
                    from kvp in kvpGroup
                    select kvp)
{
    Console.WriteLine(kvp.Key + " = " + kvp.Value);
}

【讨论】:

    【解决方案2】:

    您还没有完全清楚这些要求,但我假设您打算采用一种常见模式,即在没有决胜局的情况下得分,前 N 名有资格,如果第 N 名并列,那么所有这些并列也有资格。例如,如果您有 [10, 10, 9, 9, 8, 8],那么前 3 名将是 [10, 10, 9, 9],因为您有两个第一名、两个第三名和两个第五名。第三名及以上的每个人都有资格。但是如果你有 [10, 10, 10, 9, 9, 9] 那么只有 10 名通过,因为你有 3 个第 1 名和 3 个第 4 名——第 4 名不会晋级。维基百科称之为standard competition ranking

    可能有一种更优雅的方法来做,但直接的方法是取第一个 n,然后添加与最后一个相关的所有那些。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public static class Extension
    {
        public static IEnumerable<TSource> TopNWithTies<TSource, TKey>(this IEnumerable<TSource> sequence, Func<TSource, TKey> keySelector, int n) {
            var sequenceDescending = sequence.OrderByDescending(keySelector);
            var topN = sequenceDescending.Take(n);
            TKey cutoff = keySelector(topN.Last());
            var ties = sequenceDescending.Skip(n).TakeWhile(item => keySelector(item).Equals(cutoff));
            return topN.Concat(ties);
        }
    }
    
    public class Program
    {
        public static void Main()
        {
            Dictionary<string, int> dict = new Dictionary<string, int>();
            dict.Add("AAA", 91);
            dict.Add("BBB", 97);
            dict.Add("CCC", 98);
            dict.Add("DDD", 92);
            dict.Add("EEE", 97);
            dict.Add("FFF", 100);
            
            var top3 = dict.TopNWithTies(kvp=>kvp.Value, 3);
            
            foreach(var kvp in top3){
                Console.WriteLine(kvp.Key + " = " + kvp.Value);
            }
        }
    }
    

    如果您想计算每个项目的排名,您可以执行以下操作,将 0 索引排名应用于所有分数。基本上每个分数的排名都等于超过它的其他分数的计数。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public static class Extension
    {
        public static IEnumerable<(TSource item, int rank)> RankDescending<TSource, TKey>(this IEnumerable<TSource> sequence, Func<TSource, TKey> keySelector) {
            int rank = 0;
            foreach (var group in sequence.OrderByDescending(keySelector).GroupBy(keySelector))
            {
                foreach (var item in group)
                {
                    yield return (item, rank);
                }
                rank += group.Count();
            }
        }
    }
    
    public class Program
    {
        public static void Main()
        {
            Dictionary<string, int> dict = new Dictionary<string, int>();
            dict.Add("AAA", 91);
            dict.Add("BBB", 97);
            dict.Add("CCC", 98);
            dict.Add("DDD", 92);
            dict.Add("EEE", 97);
            dict.Add("FFF", 100);
            
            var top3 = dict.Rank(kvp=>kvp.Value).TakeWhile(item => item.rank<3);
            
            foreach(var (item, rank) in top3){
                Console.WriteLine(item.Key + " = " + item.Value + " at rank #" + rank);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      我认为 OrderByDescending 后跟 TakeWhile 可能有效。

      int lastValue=0;
      var ordered = dict.OrderByDescending(x=>x.Value);
      var result = ordered.TakeWhile((x,index)=>{
          bool takeThis = (index<3)||(x.Value==lastValue);
          lastValue=x.Value;
          return takeThis;
      });
      

      在这里提琴https://dotnetfiddle.net/JiNgaj

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-22
        • 2010-09-08
        相关资源
        最近更新 更多