How do I do an integer list intersection while keeping duplicates?

后端 未结 6 1487
别跟我提以往
别跟我提以往 2020-12-31 09:11

I\'m working on a Greatest Common Factor and Least Common Multiple assignment and I have to list the common factors. Intersection() won\'t work because that removes duplicat

相关标签:
6条回答
  • 2020-12-31 09:56

    Are you looking for something like this? It should be pretty-much O(n+m), where n is the number of items in first and m is the number of items in 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;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-31 09:58
    • Find the intersection of the two lists.
    • Group the lists by the intersecting items
    • Join the groups, and select the Min(Count) for each item
    • Flatten into a new list.

    See below:

    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();
    
    0 讨论(0)
  • 2020-12-31 10:03

    You could use this generic extension I wrote for another answer, it is essentially a single Linq statement. Note that it uses Zip to avoid the needless full enumeration of matched groups.

    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);
    }
    

    this enables,

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

    maintaining the order of the source sequence, as desired.

    0 讨论(0)
  • 2020-12-31 10:04
    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();
    

    The where expression is technically optional, I feel it makes the intent clearer.


    If you want more terse code:

    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();
    
    0 讨论(0)
  • 2020-12-31 10:04

    I wrote this extension to solve the problem:

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

    example:

    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);
    

    result:

    { 1, 2, 2, 3, 3, 4 }
    
    0 讨论(0)
  • 2020-12-31 10:09

    Here's one way to do it. To be fair, it is very similar to David B's answer except that it uses a join to do the association.

    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;
    
    0 讨论(0)
提交回复
热议问题