如何使用LINQ计算列表中的重复项数量

85

我有一个项目列表

  • John ID
  • Matt ID
  • John ID
  • Scott ID
  • Matt ID
  • John ID
  • Lucas ID

我想将它们放回到一个列表中,就像这样排序也意味着我想按最高数量的重复项进行排序。

  • John ID 3
  • Matt ID 2
  • Scott ID 1
  • Lucas ID 1

请告诉我如何使用LINQ和C#完成这个任务。

谢谢大家

编辑2显示代码:

    List<game> inventory = new List<game>();
    drinkingforDataContext db = new drinkingforDataContext();
    foreach (string item in tbTitle.Text.Split(' '))
    {

        List<game> getItems = (from dfg in db.drinkingfor_Games
                               where dfg.game_Name.Contains(tbTitle.Text)
                               select new game
                               {
                                   gameName = dfg.game_Name,
                                   gameID = Boomers.Utilities.Guids.Encoder.EncodeURLs(dfg.uid)
                               }).ToList<game>();

        for (int i = 0; i < getItems.Count(); i++)
        {
            inventory.Add(getItems[i]);
        }
    }

    var items = (from xx in inventory
                 group xx by xx into g
                 let count = g.Count()
                 orderby count descending
                 select new
                    {
                        Count = count,
                        gameName = g.Key.gameName,
                        gameID = g.Key.gameID
                    });

    lvRelatedGames.DataSource = items;
    lvRelatedGames.DataBind();

这个查询显示了以下结果:

  • 1 次“hello world”
  • 1 次“hello world”
  • 1 个“Hello World.”
  • 1 次“hello world”
  • 1 次“hello world”
  • 1 次“hello world”
  • 1 个“Hello World.”
  • 1 次“hello world”

它给出了计数和名称,但没有给出游戏的ID....

它应该显示:

  • 6 次“hello world” 234234
  • 2 个“Hello World.” 23432432

1
根据您的结果,很明显程序将所有项目视为不同 - 正如我所说,您需要实现自定义比较器,否则无法选择不同的值。 - aku
是的,那是关键部分。我不明白为什么人们会根据原始值添加答案。 - Gert Arnold
7个回答

114
你可以使用 "group by" + "orderby"。详情请参阅 LINQ 101
var list = new List<string> {"a", "b", "a", "c", "a", "b"};
var q = from x in list
        group x by x into g
        let count = g.Count()
        orderby count descending
        select new {Value = g.Key, Count = count};
foreach (var x in q)
{
    Console.WriteLine("Value: " + x.Value + " Count: " + x.Count);
}

回应这篇帖子(现已删除):

如果您有一个自定义对象列表,则需要使用自定义比较器或按特定属性分组。

此外,查询无法显示结果。请展示完整的代码以获得更好的帮助。

根据您最新的更新内容:

您有这行代码:

group xx by xx into g
由于 xx 是一个自定义对象,系统无法将一个项与另一个项进行比较。就像我之前写的一样,你需要指导编译器并提供一些属性来用于对象比较或提供自定义比较器。下面是一个例子:
请注意,我使用 Foo.Name 作为键 - 即基于 Name 属性的值对对象进行分组。
有一个问题 - 你是基于名称将2个对象视为重复,但是 Id 呢?在我的例子中,我只取组中第一个对象的 Id。如果你的对象有不同的 Id,这可能会成为一个问题。
//Using extension methods
var q = list.GroupBy(x => x.Name)
            .Select(x => new {Count = x.Count(), 
                              Name = x.Key, 
                              ID = x.First().ID})
            .OrderByDescending(x => x.Count);

//Using LINQ
var q = from x in list
        group x by x.Name into g
        let count = g.Count()
        orderby count descending
        select new {Name = g.Key, Count = count, ID = g.First().ID};

foreach (var x in q)
{
    Console.WriteLine("Count: " + x.Count + " Name: " + x.Name + " ID: " + x.ID);
}

完成了,现在你可以看到我正在看什么。 - SpoiledTechie.com
选择新的 {名称 = g.Key,计数 = count,ID = g.First().ID}; 这一行使它运转起来了。谢谢 Aku。是 .First.ID 这部分让它工作了。你很棒…… - SpoiledTechie.com
感谢 x.First().ID 的东西。 - ParPar

63

使用方法链略微缩短的版本:

var list = new List<string> {"a", "b", "a", "c", "a", "b"};
var q = list.GroupBy(x => x)
            .Select(g => new {Value = g.Key, Count = g.Count()})
            .OrderByDescending(x=>x.Count);

foreach (var x in q)
{
    Console.WriteLine("Value: " + x.Value + " Count: " + x.Count);
}

专业的简单。 - Hitesh P
使用原始值可以使其完全不同。 - Gert Arnold

9

您也可以做字典:

 var list = new List<string> { "a", "b", "a", "c", "a", "b" };
 var result = list.GroupBy(x => x)
            .ToDictionary(y=>y.Key, y=>y.Count())
            .OrderByDescending(z => z.Value);

 foreach (var x in result)
        {
            Console.WriteLine("Value: " + x.Key + " Count: " + x.Value);
        }

使用原始值会使情况完全不同。 - Gert Arnold

8

其他解决方案使用 GroupBy。由于 GroupBy 需要将所有元素保存在内存中,所以它的速度较慢。为此,我编写了自己的方法 CountBy

public static Dictionary<TKey,int> CountBy<TSource,TKey>(this IEnumerable<TSource> source, Func<TSource,TKey> keySelector)
{
    var countsByKey = new Dictionary<TKey,int>();
    foreach(var x in source)
    {
        var key = keySelector(x);
        if (!countsByKey.ContainsKey(key))
            countsByKey[key] = 0;
        countsByKey[key] += 1;
    }
    return countsByKey;
}

2

为什么不发表一个新答案呢!现在,我认为你可以这样做...
var duplicatesCount = listVariable.Count(o => o.Equals(listElement));
然后,只需使用!listVariable.Contains(elem)重复阻止器,将每个带有重复计数的元素添加到新列表中。


1
发布最新信息是个好主意。但是,“我认为”?在发布之前难道不应该确认吗? - julien.giband
再次忽略了一个事实,即不同的对象实例永远不会“相等”,即使它们的值相同。此外,这与“现在”有什么关系?这如何与问题中的代码配合使用? - undefined

-1

这是完整的程序,请检查一下

static void Main(string[] args)
{
    List<string> li = new List<string>();
    li.Add("Ram");
    li.Add("shyam");
    li.Add("Ram");
    li.Add("Kumar");
    li.Add("Kumar");

    var x = from obj in li group obj by obj into g select new { Name = g.Key, Duplicatecount = g.Count() };
    foreach(var m in x)
    {
        Console.WriteLine(m.Name + "--" + m.Duplicatecount);
    }
    Console.ReadLine();
}        

使用原始值会使情况完全不同。 - Gert Arnold

-1

另一种解决方案

public static void Main()
{
        var list = new List<string> {"a", "b", "a", "c", "a", "b"};
        list.GroupBy(x => x)
            .Select(x => new { Value = x.Key, Count = x.Count()} )
            .Where(x => x.Count > 1).ToList()
            .ForEach(x => Console.WriteLine($"Value: {x.Value} Count: {x.Count}"));
}

那并没有回答问题。对于原始值,这很容易,而且问题中的代码已经可以工作了。可悲的是,这里许多答案都忽略了这一点。 - Gert Arnold
额外的 Where 有什么好处呢?你期望存在任何 Count <= 0 的组吗?这样的组应该从哪里来? - derHugo
@derHugo,这里是Count > 1而不是Count > 0,意思是只返回分组中出现超过一次的项目,因此c不会成为结果的一部分,因为它只出现了一次,不是重复项。 - undefined
@HosamRehani 啊,我明白了,有道理。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接