【问题标题】:How do I do an integer list intersection while keeping duplicates?如何在保留重复项的同时进行整数列表交集?
【发布时间】:2011-06-28 01:42:23
【问题描述】:

我正在做一个最大公因数和最小公倍数作业,我必须列出公因数。 Intersection() 将不起作用,因为它会删除重复项。 Contains() 将不起作用,因为如果它在第二个列表中看到 int,它会从第一个列表中返回所有匹配的 int。有没有办法做一个不相干的交叉点?

编辑:抱歉没有提供示例,这就是我的意思:

如果我有套装:

{1, 2, 2, 2, 3, 3, 4, 5}
{1, 1, 2, 2, 3, 3, 3, 4, 4}

我想要输出

{1, 2, 2, 3, 3, 4}

【问题讨论】:

  • 如果 a 是 { 3,3,3,3 } 并且 b 是 { 3,3 },你期望输出中有多少个 3? 2、4 还是 6?
  • 我认为下面的答案混淆了这个问题。正确的问题是“找到两组的交集”。问题是 Intersect 运算符删除重复项 - 解决问题而不删除重复项。
  • 为什么?这会删除重复项。解释你的逻辑。
  • @JonathanGrynspan 当然它不会删除直接来自交叉操作的重复两个 3,绝对不会删除重复项!
  • 或者你可能想参考集合交集操作的基本定义。

标签: c# duplicates intersection


【解决方案1】:

我写了这个扩展来解决这个问题:

public static IEnumerable<T> Supersect<T>(this IEnumerable<T> a, ICollection<T> b)
              => a.Where(b.Remove);

示例:

var a = new List<int> { 1, 2, 2, 2, 3, 3, 4, 5 };
var b = new List<int> { 1, 1, 2, 2, 3, 3, 3, 4, 4};

var result = a.Supersect(b);

结果:

{ 1, 2, 2, 3, 3, 4 }

【讨论】:

  • 我喜欢这个,但它确实会通过清空 b 来改变它
【解决方案2】:
ILookup<int, int> lookup1 = list1.ToLookup(i => i);
ILookup<int, int> lookup2 = list2.ToLookup(i => i);

int[] result =
(
  from group1 in lookup1
  let group2 = lookup2[group1.Key]
  where group2.Any()
  let smallerGroup = group1.Count() < group2.Count() ? group1 : group2
  from i in smallerGroup
  select i
).ToArray();

where 表达式在技术上是可选的,我觉得它使意图更清晰。


如果你想要更简洁的代码:

ILookup<int, int> lookup2 = list2.ToLookup(i => i);

int[] result =
(
  from group1 in list1.GroupBy(i => i)
  let group2 = lookup2[group1.Key]
  from i in (group1.Count() < group2.Count() ? group1 : group2)
  select i
).ToArray();

【讨论】:

【解决方案3】:

您可以使用我为another answer 编写的这个通用扩展,它本质上是一个单一的Linq 语句。请注意,它使用Zip 来避免不必要的匹配组的完整枚举。

public static IEnumerable<T> Commom<T>(
        this IEnumerable<T> source,
        IEnumerable<T> sequence,
        IEqualityComparer<T> comparer = null)
{
    if (sequence == null)
    {
        return Enumerable.Empty<T>();
    }

    if (comparer == null)
    {
        comparer = EqualityComparer<T>.Default;
    }

    return source.GroupBy(t => t, comparer)
        .Join(
            sequence.GroupBy(t => t, comparer),
            g => g.Key,
            g => g.Key,
            (lg, rg) => lg.Zip(rg, (l, r) => l),
            comparer)
        .SelectMany(g => g);
}

这会启用,

new[] {1, 2, 2, 2, 3, 3, 4, 5}.Common(
    new[] {1, 1, 2, 2, 3, 3, 3, 4, 4}).ToArray()

根据需要保持源序列的顺序。

【讨论】:

    【解决方案4】:

    您正在寻找这样的东西吗?应该差不多O(n+m),其中nfirst中的项目数,m是个数second 中的项目数。

    public static IEnumerable<T> Overlap<T>(this IEnumerable<T> first,
        IEnumerable<T> second, IEqualityComparer<T> comparer = null)
    {
        // argument checking, optimisations etc removed for brevity
    
        var dict = new Dictionary<T, int>(comparer);
    
        foreach (T item in second)
        {
            int hits;
            dict.TryGetValue(item, out hits);
            dict[item] = hits + 1;
        }
    
        foreach (T item in first)
        {
            int hits;
            dict.TryGetValue(item, out hits);
            if (hits > 0)
            {
                yield return item;
                dict[item] = hits - 1;
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      这是一种方法。公平地说,它与 David B 的答案非常相似,只是它使用连接来进行关联。

      IEnumerable<Foo> seqA = ...
      IEnumerable<Foo> seqB = ...
      
      var result = from aGroup in seqA.GroupBy(x => x)
                   join bGroup in seqB.GroupBy(x => x) 
                               on aGroup.Key equals bGroup.Key
                   let smallerGroup = aGroup.Count() < bGroup.Count() 
                                      ? aGroup : bGroup
                   from item in smallerGroup
                   select item;
      

      【讨论】:

        【解决方案6】:
        • 找到两个列表的交集。
        • 按交叉项对列表进行分组
        • 加入组,并为每个项目选择 Min(Count)
        • 展平成一个新列表。

        见下文:

        var intersect = list1.Intersect(list2).ToList();
        var groups1 = list1.Where(e => intersect.Contains(e)).GroupBy(e => e);
        var groups2 = list2.Where(e => intersect.Contains(e)).GroupBy(e => e);
        
        var allGroups = groups1.Concat(groups2);
        
        return allGroups.GroupBy(e => e.Key)
            .SelectMany(group => group
                .First(g => g.Count() == group.Min(g1 => g1.Count())))
            .ToList();
        

        【讨论】:

          猜你喜欢
          • 2014-12-27
          • 1970-01-01
          • 2012-02-09
          • 2022-01-14
          • 1970-01-01
          • 2010-10-03
          • 1970-01-01
          相关资源
          最近更新 更多