使用LINQ计算列表中某个项出现的次数

11
我试图使用LINQ计算列表中一个项的出现次数,
我有以下模式 -
用户(提供所有条目), 计数(待计算)
计数应该是这样的 -
我无法想到一个优雅的解决方案。
仅使用LINQ是否可能?如果不行,我们如何使用带有一些C#代码的LINQ实现它。

2
你期望的输出是什么?按用户分组的出现次数计数(而不是求和),还是带有出现次数的附加列? - Jim Wooley
看起来 GroupBy 很容易让你计算每个用户名的出现次数? - Jon
他询问每行从顶部开始的出现次数计数,而不是组中元素的数量。 - Tarec
你有一个用于排序行的列吗?如果没有,如何确保项目的顺序? - Ivo
4个回答

3
可能不容易用LINQ完成。但你可以使用自己的扩展方法相对容易地完成。(我实际上还没有尝试编译和运行代码,所以不能保证它能正常工作,但它绝对是一个很好的起点)。
public static IEnumerable<Tuple<T, int>> RunningTotal<T>(this IEnumerable<T> source)
{
    var counter = new Dictionary<T, int>();

    foreach(var s in source)
    {
        if(counter.ContainsKey(s))
        {
            counter[s]++;
        }
        else
        {
            counter.Add(s, 1);
        }

        yield return Tuple.Create(s, counter[s]);
    }
}

通过一些微调(编辑处于队列中),它的工作非常好。如果需要的话,代码还可以进一步压缩。将"== false"添加到条件语句中,并删除else和自增操作,让计数器[s]++来完成任务即可。除此之外,这是一个非常漂亮的解决方案。 - Fredrik Ljung

3

您可以通过结合循环和Enumerable.Take来实现,例如:

for (int i = 0; i < list.Count; i++)
{
    //Get count of current element to before:
    int count = list.Take(i+1)
                    .Count(r => r.UserName == list[i].UserName);
    list[i].Count = count;
}

你的`list`是这样定义的:
List<User> list = new List<User>
    {
        new User{UserName = "A"},
        new User{UserName = "B"},
        new User{UserName = "A"},
        new User{UserName = "A"},
        new User{UserName = "B"},
        new User{UserName = "A"},
        new User{UserName = "C"},
        new User{UserName = "A"},

    };

并将 User 类定义如下:

public class User
{
    public string UserName { get; set; }
    public int Count { get; set; }
}

稍后您可以像这样打印输出:
foreach (var item in list)
{
    Console.WriteLine("UserName: {0}, Running Total: {1}", item.UserName, item.Count);
}

然后您将收到:

UserName: A, Running Total: 1
UserName: B, Running Total: 1
UserName: A, Running Total: 2
UserName: A, Running Total: 3
UserName: B, Running Total: 2
UserName: A, Running Total: 4
UserName: C, Running Total: 1
UserName: A, Running Total: 5

这是n平方阶的算法。对于小型列表来说还不错,但是对于大型列表或者来自数据库的枚举,有更好的方法。 - Ian Mercer

1
你可以这样做,无需对集合进行双重迭代
        var values = "ABAABCA".ToArray();

        var counts = new Dictionary<char, int>();

        var counter = values.Select(ch => 
            { 
                int count = counts[ch] = counts.ContainsKey(ch) ? counts[ch] + 1 : 1;
                return new {ch, count}; 
            });

        foreach (var c in counter)
            Console.WriteLine(c.ch + " -> " + c.count);

0

我使用了Select来完成这个任务。我正在编写一个同步函数,从魔兽世界API获取物品,但某些物品已经不在API中了,尽管它们仍然存在于游戏中。由于我按照拍卖行上列出的次数顺序同步物品,所以会卡在这些特定的物品上。

我创建了一个坏物品ID列表。如果物品无法同步,我就将其添加到列表中。如果无法同步3次,我想跳过该物品。我用以下代码实现了这一点:

private int[] GetBadItemList()
{
    var ids = from i in _badItemIDs
              where _badItemIDs.Select(curItem => curItem.Equals(i)).Count() >= MAX_ITEM_TRYS
              select i;
    return ids.ToArray();
}

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