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?一个简单的迭代器块很好地解决了这个问题。
下面的代码假定您已在对象中覆盖 Equals
和 GetHashCode
以检查 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<IEnumerable<T>>
作为返回值,另一种只扫描输入一次的方法可能是这样的(不创建中间列表):
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:在列表中的列表上分组的主要内容,如果未能解决你的问题,请参考以下文章