使用LINQ对组列表条目进行分类

7

我有以下模型:

public class Entry
{
    public int UseraccountId { get; set; }
    public int CompanyId { get; set; }
    public DateTime CreationDate { get; set; }
    public string Target { get; set; }
    public string Message { get; set; }
}

还有很多条目的列表:

List<Entry> entries = ... //get all entries.

示例:

分组前的示例

现在我想将第2行和第3行分组,因为它们具有相同的UserId、CompanyId、target和 几乎 相同的日期时间(这是困难的部分),假设在5秒的范围内。

分组后,我的列表应该如下所示:

输入图像描述

是否有任何简单的方法解决这个问题? 有什么建议吗? 我相信Linq会帮助我解决问题,但我不确定怎么做。

编辑: 感谢大家的反馈。 我决定更改设计,并确保日期时间现在真的相同。 因此,使用linq分组现在非常容易。


当你说“几乎”时,你希望将其分组,是在同一分钟内还是同样的10秒钟内? - Chris Dixon
假设在5秒的范围内。 - mosquito87
3
按照相同的值进行分组很简单;查看相关的许多问题即可。按照几乎相同的值进行分组则有点困难,因为如果X几乎等同于Y,而Y几乎等同于Z,则不能断定X几乎等同于Z。你需要某种聚类算法 - dtb
3个回答

1
如@dtb所提到的,按“关闭”分组很困难,因为您可能会得到比预期更大的“桶”。例如,如果您有100个条目,它们相互之间相差4秒钟,那么将在“下一个”项目之内的5秒钟内创建的项目分组将把所有项目放入一个桶中!
但是,如果您想将创建日期舍入到最近的5秒钟,然后进行分组,您可以使用:
TimeSpan ts = new TimeSpan(0, 0, 5);  // 5 seconds
entries.GroupBy(i => new {
                          UserId = i.UserId, 
                          CompanyId = i.CompanyId, 
                          Target = i.Target, 
                          RoundedTime = DateTime.MinValue.AddTicks(
                                            (long)(Math.Round((decimal)i.CreationDate.Ticks / ts.Ticks) * ts.Ticks)
                                        ) ;
                          ))
       .Select(g => new {
                         UserId = g.Key.UserId, 
                         CompanyId = g.Key.CompanyId, 
                         Target = g.Key.Target, 
                         RoundedTime = g.Key.RoundedTime,
                         Message = string.Join(", ",g.Select(i=> i.Message).ToArray())
                        } );

这将按最接近5秒的四舍五入为一组 - 有可能相差1秒的两个项目会在不同的桶中,但您不必担心交换律的问题。


0

这将给出一个范围为-5秒,但对于CreationDate的完全匹配(聚类)算法,我猜它会更加困难。不过你明白我的意思。

List<Entry> entries = entries.GroupBy(a => a.UserId)
                             .ThenBy(a => a.CompanyId)
                             .ThenBy(a => a.CreationDate.AddSeconds(-5));

实际上,即使对于 CreationDate,这也不能正常工作,你首先必须将秒钟扁平化。 - Chris Dixon
尝试整数除法?然后按(a => TimeSpan.FromTicks(a.CreationDate.Ticks).TotalSeconds / 5)排序; - penguat
我认为你不能使用ThenBy链接分组,除非它是某个Linq扩展库的一部分。 - D Stanley
我在我的当前.NET 4.5项目中经常使用ThenBy(System.Linq.Enumerable命名空间)。我一直认为它只是LINQ库的一部分,因为我没有使用任何扩展库。 - Chris Dixon

0

这个问题没有直接的答案,因为这取决于你认为什么是匹配。有简单和复杂的方法以及介于两者之间的方法。你需要想出算法。一个简单的方法是将秒数删掉,只匹配到分钟,但这可能太长了。你可以编写一个方法,将时间戳规范化为5或10秒,并按建议进行分组。

如果你想将任何两条消息在x秒内分组在一起,那么这种方法只能基本上起作用。总会有那些在范围内但落在截止点两侧的值。如果你可以接受这一点并且重视简单性,那么上面的答案就可以使用。

如果这不起作用,你想跨越人为的截止点进行分组,那么你需要另一种方法。在这种情况下,一个简单的方法是使用LINQ按除时间戳外的所有内容进行分组。这将对你的数据进行初步分组。然后你可以遍历每个组,并将同一组中的每个时间值与其他时间值进行比较,确定它是否在你的范围内。然后手动抓取那些落在指定范围内的值并将它们分组在一起。

这个问题还有一个额外的特例,你需要做出决策。如果你决定在1秒内进行分组,并且有三个条目,其秒数分别为(简化)1、2和3。1和2在一秒内,2和3也在一秒内,但是1和3不在一秒内。你会根据2与其他条目相隔1秒来分组吗?还是将1和2分为一组,使得2无法与3分组,而3将单独成组。

最终你将得到一个解决方案,它的桶可以根据链接值的增长而增长,或者根据第一个分组创建的时间截止而使用不同的人为截止。使用固定的时间截止要简单得多,所以除非你需要不断增长的桶,我建议只需使用标准化的时间戳进行分组。

你需要定义“几乎”的意思,并相应地制定计划。


谢谢您的想法。它让我思考我的设计,我决定进行更改,以便现在的日期时间确实相同,并且分组现在非常容易。 - mosquito87

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