【问题标题】:Find all intersecting data, not just the unique values查找所有相交数据,而不仅仅是唯一值
【发布时间】:2010-02-01 21:00:44
【问题描述】:

我以为我理解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 也完全没问题。

问题是我在一个非常大的集合上使用它,所以我需要效率。

我们谈论的是对象,而不是整数。整数只是为了简单的示例,但我意识到这可以有所作为。

【问题讨论】:

  • 鉴于您的更新,可能会有更有效的方法来解决您的问题。告诉我们更多关于数据的信息。具体来说,我对您的大型收藏是否主要包含独特元素或主要重复的问题感兴趣。我也很想知道元素是否真的是整数,或者这是否是一些更复杂类型的替代;具体来说,您的数据是否定义了总排序?也就是说,给定一组此类数据,是否存在唯一的、明确定义的从小到大的排序?

标签: c# linq intersection


【解决方案1】:

让我们看看我们是否可以准确地描述您想要的内容。如果我错了,请纠正我。您想要:列表 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();

它只进行几千次比较,但可能会使用更多内存。

【讨论】:

  • 您的 LINQ 查询与其他两个查询的结果不同 - 如果元素 e 在 list1 中出现 n 次,在 list2 中出现 m 次,则它们包含它 n*m 次,这不是所需的行为。
  • 优秀的捕获 @kvb。我完全错过了这一点,因为在给定的示例中,它们恰好看起来令人困惑地相似。我将删除不正确的代码。谢谢!
  • 对 HashSet 很感兴趣。我不知道它更有效率。会调查的!
  • @Peterdk:列表是 O(n) 来测试元素的成员资格,但作为交换,您可以 (1) 保持顺序,并且 (2) 有重复项。 HashSet 是 O(1) 来测试元素的成员资格,但不保持元素有序且从不包含重复项。如果你愿意加倍内存并同时使用 HashSet List,你可以获得两全其美的效果。
【解决方案2】:
var set = new HashSet(list1.Intersect(list2));
return list1.Concat(list2).Where(i=>set.Contains(i));

【讨论】:

    【解决方案3】:

    也许这会有所帮助:https://gist.github.com/mladenb/b76bcbc4063f138289243fb06d099dda

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

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

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

    【讨论】:

      【解决方案4】:

      我不相信使用内置 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;
          }
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-23
        • 1970-01-01
        • 2020-10-12
        相关资源
        最近更新 更多