【问题标题】:How to get top N elements of a list with LinQ?如何使用 LinQ 获取列表的前 N ​​个元素?
【发布时间】:2014-09-29 12:23:17
【问题描述】:

我有一个按考试分数排序的列表,我想拥有该列表的前 N ​​个元素。
如果 N(th) 和 N+1(th) 个学生的考试分数相同,则列表必须同时包含它们。

例如我有一个这样的列表:

john.   80  
mike.   75  
james.  70  
ashley. 70
kate.   60

前 3 名应返回 john, mike, james, ashley
我试过 Take() 但它只返回 john, mike, james

英语不是我的主要语言,如果我说的不正确,请见谅
谢谢

【问题讨论】:

  • 基本上,你的意思是WITH TIES,用 SQL 术语来说,是吗?
  • @Shaharyar 我试过这个stackoverflow.com/questions/319973/…
  • Take(n) 不考虑决胜局
  • 如果两个以上的学生成绩相同怎么办?你会把它们都拿走吗?
  • @AhmetEmre90:您能否通过编辑您的问题并提供更有意义的示例数据来阐明您的要求?例如,如果有两个 80、两个 70 和两个 60,你想要获得 6 个项目,还是想要获得两个 80 + 两个 70?

标签: c# linq


【解决方案1】:

这是一个只通过一次的实现:

public static IEnumerable<TSource> TopWithTies<TSource, TValue>(
    this IEnumerable<TSource> source,
    int count,
    Func<TSource, TValue> selector)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    if (count < 0) throw new ArgumentOutOfRangeException("count");
    if (count == 0) yield break;
    using(var iter = source.OrderByDescending(selector).GetEnumerator())
    {
        if(iter.MoveNext())
        {
            yield return iter.Current;
            while (--count >= 0)
            {
                if(!iter.MoveNext()) yield break;
                yield return iter.Current;    
            }
            var lastVal = selector(iter.Current);
            var eq = EqualityComparer<TValue>.Default;
            while(iter.MoveNext() && eq.Equals(lastVal, selector(iter.Current)))
            {
                yield return iter.Current;
            }
        }
    }
}

示例用法:

var data = new[]
{
    new { name = "john", value = 80 },
    new { name = "mike", value = 75 },
    new { name = "james", value = 70 },
    new { name = "ashley", value = 70 },
    new { name = "kate", value = 60 }
};
var top = data.TopWithTies(3, x => x.value).ToList();
foreach(var row in top)
{
    Console.WriteLine("{0}: {1}", row.name, row.value);
}

【讨论】:

    【解决方案2】:

    如果两个以上的学生成绩相同怎么办?你会把它们都拿走吗? OP:是的

    您可以按点分组,然后使用OrderByDescending + Take + SelectMany

    var topThreePoints = users.GroupBy(u => u.Points)
                              .OrderByDescending(g => g.Key)
                              .Take(3)
                              .SelectMany(g => g);
    

    【讨论】:

    • 这需要前 3 组,但是 - 如果第 1 组有 200 件物品,您不会从任何其他组中获取任何物品
    • @Marc,我不明白你的评论。如果第一组有 200 个项目,SelectMany() 项目来自前 3 个组,那么结果肯定会包括这 200 个项目加上其他两个组中的项目,对吧?
    • @FrédéricHamidi 如果意图是 TOP WITH TIES,则不是。
    • @MarcGravell:也许我对这个问题的理解不正确。但我想 OP 想要包括所有的关系。
    • 对于它的价值,我阅读问题的方式与蒂姆相同。另请注意,提问者从未承认 Marc 的评论,而且可能根本不是在寻找 WITH TIES 解决方案——目前尚不清楚。
    【解决方案3】:

    你可能想做的是

    1. 获得第 n 个
    2. 当 >= nth 时获取全部

    var nth = users.Skip(n-1).FirstOrDefault()
    var top = users.TakeWhile(user => user.Score >= nth.Score)
    

    (这假定列表是按降序排列的,如问题中给出的示例。如果输入列表中有

    【讨论】:

      【解决方案4】:

      我在 LINQPad 中创建了一个示例案例。

      var a = new List<Tuple<string,int>>();
      a.Add(new Tuple<string,int>("john",80));
      a.Add(new Tuple<string,int>("mike",75));
      a.Add(new Tuple<string,int>("james",70));
      a.Add(new Tuple<string,int>("ashley",70 ));
      a.Add(new Tuple<string,int>("kate",60  ));
      
      a.Where(x=>x.Item2>=a.OrderBy(i=>i.Item2).Skip(2).Take(1).SingleOrDefault ().Item2).Dump();
      

      不知道它是否足够有效。

      【讨论】:

        【解决方案5】:

        也许是这样?

        list.TakeWhile((item, index) => index < N || list[index] == list[index + 1]);
        

        【讨论】:

        • 这看起来不错,但它可能会索引list[list.Length]。我认为list[index] == list[index - 1] 没有这个问题,因为index &lt; Nindex 为0 时成立。
        猜你喜欢
        • 1970-01-01
        • 2010-09-24
        • 1970-01-01
        • 2013-06-19
        • 2017-01-21
        • 2020-05-01
        • 2010-12-21
        • 2014-07-23
        • 2011-03-28
        相关资源
        最近更新 更多