Linq:在列表中的列表上分组

Posted

技术标签:

【中文标题】Linq:在列表中的列表上分组【英文标题】:Linq: Group on list inside lists 【发布时间】:2013-12-20 18:08:59 【问题描述】:

假设我有一个列表列表。 对于此列表中的每个项目,我都有一个自定义对象列表。

这些对象是这样的:

public string Field1
public string Field2
public string Field3

我想通过 Linq 实现的目标:从我的列表列表中过滤出所有具有相同三个字段的对象,这些字段不是其列表的第一个元素,并且只保留第一个。

假设我的列表中有两个列表 listA 和列表 B。

listA 包含三个对象 object1、object2 和 object3。

object1.Field1 = "a"    object1.Field2 = "A"    object1.Field3 = "1"  
object2.Field1 = "a"    object2.Field2 = "B"    object2.Field3 = "2"  
object3.Field1 = "a"    object3.Field2 = "C"    object3.Field3 = "3"  

listB 包含三个对象 object4、object5 和 object6。

object4.Field1 = "a"    object4.Field2 = "A"    object4.Field3 = "1"  
object5.Field1 = "a"    object5.Field2 = "B"    object5.Field3 = "2"  
object6.Field1 = "a"    object6.Field2 = "D"    object6.Field3 = "3" 

在这个例子中,object1 和 object4 是相同的,但是因为它们在各自的列表中排在第一位,所以没有被过滤掉。 但是,object2 和 object5 具有相同的三个字段值,只保留其中一个,以便在我的流程结束时,我将拥有两个列表,如下所示:

listA 包含三个对象 object1、object2 和 object3。

object1.Field1 = "a"    object1.Field2 = "A"    object1.Field3 = "1"  
object2.Field1 = "a"    object2.Field2 = "B"    object2.Field3 = "2"  
object3.Field1 = "a"    object3.Field2 = "C"    object3.Field3 = "3"

listB 现在有两个对象 object4 和 object6。

object4.Field1 = "a"    object4.Field2 = "A"    object4.Field3 = "1"  
object6.Field1 = "a"    object6.Field2 = "D"    object6.Field3 = "3" 

我为此苦苦思索了好几个小时,但无济于事。我不能对所有其他列表进行 foreach 列表查看,因为这会导致性能问题(我可能有 1000000 个列表列表)。

有人对此有想法吗?

【问题讨论】:

我认为 1000000 个列表列表会有非常大的性能问题。是否有任何选项可以从数据库中获取过滤后的数据? @lazyberezovsky 不幸的是,这不是我的选择。你是对的,它本来会简单得多,但我不能那样做。我想我能做的是添加一个 id 字段,然后我可以获取相同的对象的 id,然后将它们从我的列表中过滤出来。问题是如何通过我列表中的所有不同列表按 Field1、Field2 和 Field3 进行分组。 @lazyberezovsky 它也没有效率。我将把列表列表放在一个 xml 文件中,并使用它来删除我要过滤的节点... 所以您的输入是 n 个列表,并且您想要所有列表中每个对象的一个​​副本(不包括第一个元素)?关于第一个元素要求,如果 object4 是第二个列表的第二个项目会怎样。您是否也希望将其过滤掉?我有一个解决方案的想法,但我不确定它是否会像你希望的那样有 1000000 个列表。 【参考方案1】:

为什么必须是 LINQ?一个简单的迭代器块很好地解决了这个问题。

下面的代码假定您已在对象中覆盖 EqualsGetHashCode 以检查 3 个字段的相等性。如果这不可能,请改用自定义相等比较器(在 HashSet 构造函数中传递)

static IEnumerable<List<T>> GetFilteredList<T>(IEnumerable<List<T>> input)

    var found = new HashSet<T>();

    foreach (var list in input)
    
        var returnList = new List<T>(list.Capacity);
        foreach (var item in list)
        
            // the first item is always added
            // any other items are only added if they were 
            // never encountered before
            if (list.Count == 0 || !found.Contains(item))
            
                found.Add(item);
                returnList.Add(item);
            
        
        yield return returnList;
    

如果您可以坚持使用IEnumerable&lt;IEnumerable&lt;T&gt;&gt; 作为返回值,另一种只扫描输入一次的方法可能是这样的(不创建中间列表):

static IEnumerable<IEnumerable<T>> GetFilteredList<T>(IEnumerable<List<T>> input)

    var encounteredElements = new HashSet<T>();

    foreach (var list in input)
    
        yield return Process(list, encounteredElements);
    


static IEnumerable<T> Process<T>(IEnumerable<T> input, 
                                 HashSet<T> encounteredElements)

    bool first = true;
    foreach (var item in input)
    
        if (first) yield return item;
        if (!encounteredElements.Contains(item))
        
            yield return item;
        
        encounteredElements.Add(item);
        first = false;
    

【讨论】:

如果我的输入列表中有大量列表,会不会出现性能问题? 好吧,循环遍历所有项目的性能如何?您将无法做得比这更好,在这种情况下,您可能希望查看内存处理以外的其他解决方案。

以上是关于Linq:在列表中的列表上分组的主要内容,如果未能解决你的问题,请参考以下文章

Linq:根据另一个列表分组和求和

C# LINQ - 按属性分组列表,然后按不同组选择

如何通过 LINQ 扩展获取 group 的分组值列表

Linq to sql,聚合列,按日期分组到列表视图

LINQ分组[重复]

Linq,EF Core - 按一个字段分组并使用其他字段从其他表中获取数据列表