6

When I have 2 List<string> objects, then I can use Intersect and Except on them directly to get an output IEnumerable<string>. That's simple enough, but what if I want the intersection/disjuction on something more complex?

Example, trying to get a collection of ClassA objects which is the result of the intersect on ClassA object's AStr1 and ClassB object's BStr; :

public class ClassA {
    public string AStr1 { get; set; }
    public string AStr2 { get; set; }
    public int AInt { get; set; }
}
public class ClassB {
    public string BStr { get; set; }
    public int BInt { get; set; }
}
public class Whatever {
    public void xyz(List<ClassA> aObj, List<ClassB> bObj) {
        // *** this line is horribly incorrect ***
        IEnumberable<ClassA> result =
            aObj.Intersect(bObj).Where(a, b => a.AStr1 == b.BStr);
    }
}

How can I fix the noted line to achieve this intersection.

Diana
  • 61
  • 1
  • 2

3 Answers3

14

MoreLINQ has ExceptBy. It doesn't have IntersectBy yet, but you could easily write your own implementation, and possibly even contribute it to MoreLINQ afterwards :)

It would probably look something like this (omitting error checking):

public static IEnumerable<TSource> IntersectBy<TSource, TKey>(
    this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer)
{
    HashSet<TKey> keys = new HashSet<TKey>(first.Select(keySelector),
                                           keyComparer);
    foreach (var element in second)
    {
        TKey key = keySelector(element);
        // Remove the key so we only yield once
        if (keys.Remove(key))
        {
            yield return element;
        }
    }
}

If you wanted to perform an intersection on two completely different types which happened to have a common property type, you could make a more general method with three type parameters (one for first, one for second, and one for the common key type).

Dmitrii Dovgopolyi
  • 6,231
  • 2
  • 27
  • 44
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Hi @Jon Skeet , I don't understand how can I pass the fourth parameter. Am I define a class by implementing IEqualityComparer and then pass an instance of this class? – corei11 Nov 28 '16 at 06:53
  • @corei11: Yes, if you need something custom - or use `EqualityComparer.Default` if you're happy with the default equality operation. – Jon Skeet Nov 28 '16 at 06:54
  • Thanks @Jon Skeet. Here I have two `IEnumerable owner1` and `owner2`. And I define a class named `Compar` by implementing `IEqualityComparer`. Now call the method by this way `owner1 = owner1.IntersectBy(owner2, item => item.Email_Address, new Compar())`. Actually I want to clear that how pass my Compar class. That is directly I write string or there any other better way or dows my approach is ok? My Compar class prototype is `public class Compar : IEqualityComparer`. Sorry. It's may be a silly question. Thanks. – corei11 Nov 28 '16 at 07:25
  • @corei11: At this point I think you should be asking a new question with a [mcve]. – Jon Skeet Nov 28 '16 at 07:25
3

x ∈ A ∩ B if and only if x ∈ A and x ∈ B.

So, for each a in aObj, you can check if a.AStr1 is in the set of BStr values.

public void xyz(List<ClassA> aObj, List<ClassB> bObj)
{
    HashSet<string> bstr = new HashSet<string>(bObj.Select(b => b.BStr));
    IEnumerable<ClassA> result = aObj.Where(a => bstr.Contains(a.AStr1));
}
dtb
  • 213,145
  • 36
  • 401
  • 431
1

this code:

    public IEnumerable<ClassA> xyz(List<ClassA> aObj, List<ClassB> bObj)
    {
        IEnumerable<string> bStrs = bObj.Select(b => b.BStr).Distinct();
        return aObj.Join(bStrs, a => a.AStr1, b => b, (a, b) => a);
    }

has passed the following test:

    [TestMethod]
    public void PropertyIntersectionBasedJoin()
    {
        List<ClassA> aObj = new List<ClassA>()
                                {
                                    new ClassA() { AStr1 = "a" }, 
                                    new ClassA() { AStr1 = "b" }, 
                                    new ClassA() { AStr1 = "c" }
                                };
        List<ClassB> bObj = new List<ClassB>()
                                {
                                    new ClassB() { BStr = "b" }, 
                                    new ClassB() { BStr = "b" }, 
                                    new ClassB() { BStr = "c" }, 
                                    new ClassB() { BStr = "d" }
                                };

        var result = xyz(aObj, bObj);

        Assert.AreEqual(2, result.Count());
        Assert.IsFalse(result.Any(a => a.AStr1 == "a"));
        Assert.IsTrue(result.Any(a => a.AStr1 == "b"));
        Assert.IsTrue(result.Any(a => a.AStr1 == "c"));
    }
AntonioR
  • 584
  • 3
  • 6