查找所有相交的数据,而不仅仅是唯一值。

14

我原本以为我理解了Intersect,但事实证明我错了。

 List<int> list1 = new List<int>() { 1, 2, 3, 2, 3};
 List<int> list2 = new List<int>() { 2, 3, 4, 3, 4};

 list1.Intersect(list2) =>      2,3

 //But what I want is:
 // =>  2,3,2,3,2,3,3

我可以想出一种方法,例如:

 var intersected = list1.Intersect(list2);
 var list3 = new List<int>();
 list3.AddRange(list1.Where(I => intersected.Contains(I)));
 list3.AddRange(list2.Where(I => intersected.Contains(I)));
有没有更简单的LINQ方法可以实现这个?需要说明的是,我不关心结果的顺序。2,2,2,3,3,3,3 也完全可以接受。问题是我在一个非常大的集合上使用它,所以我需要效率。我们谈论的是对象而不是int。int只是为了举个简单的例子,但我意识到这可能会有所不同。

根据您的更新,可能有更有效的方法来解决您的问题。告诉我们更多关于数据的信息。具体而言,我对以下问题感兴趣:您的大型集合是否主要包含唯一元素或重复元素?我还想知道这些元素是否真的是整数,还是代表某种更复杂类型;特别地,您的数据是否定义了全序?也就是说,给定这些数据的集合,是否存在唯一、明确定义的从最小到最大的排序方式? - Eric Lippert
4个回答

20

让我们看看是否可以准确地描述您想要的东西。如果我错了请纠正我。 您想要:列表1中按顺序出现在列表2中的所有元素,后跟列表2中按顺序出现在列表1中的所有元素。 是吗?

看起来很简单。

return list1.Where(x=>list2.Contains(x))
     .Concat(list2.Where(y=>list1.Contains(y)))
     .ToList();

请注意,对于大型列表,这种方法不是高效的。如果每个列表都有一千个项目,则会进行数百万次比较。如果您处于这种情况下,则需要使用更有效的数据结构来测试成员资格:

list1set = new HashSet(list1);
list2set = new HashSet(list2);

return list1.Where(x=>list2set.Contains(x))
     .Concat(list2.Where(y=>list1set.Contains(y)))
     .ToList();

该算法只进行了数千次比较,但可能使用更多的内存。


5
你的LINQ查询结果与其他两个查询结果不同 - 如果元素e在list1中出现n次,在list2中出现m次,则它们包含n*m次,这不是期望的行为。 - kvb
2
很好的发现,@kvb。我完全错过了,因为在给定的示例中,它们看起来非常相似。我会删除错误的代码。谢谢! - Eric Lippert
HashSet很有趣。我不知道它更有效率。会去了解一下! - Peterdk
@Peterdk:列表在测试元素成员身份时的时间复杂度为O(n),但是作为交换,它们可以让你(1)维护一个顺序,和(2)拥有重复元素。哈希集合在测试元素成员身份时的时间复杂度为O(1),但是不保持元素顺序并且从不包含重复元素。如果你愿意将内存翻倍并同时使用哈希集合和列表,你就可以得到两全其美的效果。 - Eric Lippert

1
var set = new HashSet(list1.Intersect(list2));
return list1.Concat(list2).Where(i=>set.Contains(i));

0

也许这可以帮助你:https://gist.github.com/mladenb/b76bcbc4063f138289243fb06d099dda

原始的Except/Intersect返回一个唯一项的集合,尽管它们的契约没有说明(例如,这些方法的返回值不是HashSet/Set,而是IEnumerable),这可能是一个糟糕的设计决策的结果。相反,我们可以使用更直观的实现,它返回第一个枚举中与之相同的元素,而不仅仅是唯一的元素(使用Set.Contains)。

此外,添加了映射函数以帮助交叉/排除不同类型的集合。

如果您不需要交叉/排除不同类型的集合,只需检查Intersect/Except的源代码,并更改遍历第一个枚举的部分,以使用Set.Contains而不是Set.Add/Set.Remove。


-1

我不相信使用内置的API可以实现这一点。但是,您可以使用以下方法来获得您要查找的结果。

IEnumerable<T> Intersect2<T>(this IEnumerable<T> left, IEnumerable<T> right) {
  var map = left.ToDictionary(x => x, y => false);
  foreach ( var item in right ) {
    if (map.ContainsKey(item) ) {
      map[item] = true;
    }
  }
  foreach ( var cur in left.Concat(right) ) {
    if ( map.ContainsKey(cur) ) {
      yield return cur;
    }
  }
}

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