26

I've got a class:

class ThisClass
{
  private string a {get; set;}
  private string b {get; set;}
}

I would like to use the Intersect and Except methods of Linq, i.e.:

private List<ThisClass> foo = new List<ThisClass>();
private List<ThisClass> bar = new List<ThisClass>();

Then I fill the two lists separately. I'd like to do, for example (and I know this isn't right, just pseudo code), the following:

foo[a].Intersect(bar[a]);

How would I do this?

David Archer
  • 2,008
  • 4
  • 26
  • 31

7 Answers7

41

If you want a list of a single property you'd like to intersect then all the other pretty LINQ solutions work just fine. BUT! If you'd like to intersect on a whole class though and as a result have a List<ThisClass> instead of List<string> you'll have to write your own equality comparer.

foo.Intersect(bar, new YourEqualityComparer());

same with Except.

public class YourEqualityComparer: IEqualityComparer<ThisClass>
{

    #region IEqualityComparer<ThisClass> Members


    public bool Equals(ThisClass x, ThisClass y)
    {
        //no null check here, you might want to do that, or correct that to compare just one part of your object
        return x.a == y.a && x.b == y.b;
    }


    public int GetHashCode(ThisClass obj)
    {
        unchecked
        {
            var hash = 17;
                            //same here, if you only want to get a hashcode on a, remove the line with b
            hash = hash * 23 + obj.a.GetHashCode();
            hash = hash * 23 + obj.b.GetHashCode();

            return hash;    
        }
    }

    #endregion
}
Patryk Ćwiek
  • 14,078
  • 3
  • 55
  • 76
38

Maybe

// returns list of intersecting property 'a' values
foo.Select(f => f.a).Intersect(bar.Select(b => b.a));

BTW property a should be public.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • 5
    this returns a collection of Property. I was looging for return of Object collection. See comment Below from Patryk. – Jakub Sluka Jan 06 '21 at 10:48
8

Not sure of the speed of this compared to intersect and compare but how about:

//Intersect
var inter = foo.Where(f => bar.Any(b => b.a == f.a));
//Except - values of foo not in bar
var except = foo.Where(f => !bar.Any(b => b.a == f.a));
Zach Johnson
  • 682
  • 7
  • 14
  • 5
    This is an O(n * m) algorithm whereas `Intersect` and `Except` are both `O(n + m)`. That makes your quite a lot worse. It also iterates `bar` multiple times, which can be a major problem in all sorts of situations (it might not yield the same results on each iteration, it could query a database or preform expensive computation on each iteration, it could have side effects caused when iterated, etc. – Servy Jan 14 '15 at 18:07
  • Extension of the @Servy answer: just look at the [source code](https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L864) to evaluate the complexity of *Intersect* / *Except* methods. – vladimir May 07 '19 at 02:00
2
foo.Select(x=>x.a).Intersect(bar.Select(x=>x.a))
Tilak
  • 30,108
  • 19
  • 83
  • 131
0

What exactly is the desired effect? Do you want to get a list of strings composed of all the a's in your classes, or a list of ThisClass, when two ThisClass instances are identified via unique values of a?

If it's the former, the two answers from @lazyberezovksy and @Tilak should work. If it's the latter, you'll have to override IEqualityComparer<ThisClass> or IEquatable<ThisClass> so that Intersect knows what makes two instances of ThisClass equivalent:

 private class ThisClass : IEquatable<ThisClass>
 {
     private string a;

     public bool Equals(ThisClass other)
     {
        return string.Equals(this.a, other.a);
     }
 }

then you can just call:

 var intersection = foo.Intersect(bar);     
Avner Shahar-Kashtan
  • 14,492
  • 3
  • 37
  • 63
  • 1
    You always need to override `GetHashCode` when implementing `IEquatable`. Since you don't, this won't work. – Servy Jan 14 '15 at 18:09
0

I know this is old but couldn't you also just override the Equals & GetHashCode on the class itself?

class ThisClass
{
  public string a {get; set;}
  private string b {get; set;}

  public override bool Equals(object obj)
  {
    // If you only want to compare on a
    ThisClass that = (ThisClass)obj;
    return string.Equals(a, that.a/* optional: not case sensitive? */);
  }

  public override int GetHashCode()
  {
    return a.GetHashCode();
  }
}
tyr
  • 21
  • 1
  • 6
-3

You should create IEqualityComparer. You can pass the IEqualityComparer to Intersect() method. This will help you get List(which intersect with bar) easier.

var intersectionList = foo.Intersect(bar, new ThisClassEqualityComparer()).ToList();


class ThisClassEqualityComparer : IEqualityComparer<ThisClass>
{

    public bool Equals(ThisClass b1, ThisClass b2)
    {
        return b1.a == b2.a;
    }


    public int GetHashCode(Box bx)
    {
       // To ignore to compare hashcode, please consider this.
       // I would like to force Equals() to be called
       return 0;
    }

}
Pongsathon.keng
  • 1,417
  • 11
  • 14
  • 1
    You shouldn't be returning `0` from the hash code like this. That's going to completely kill performance. You should instead use the hash code of `a`. – Servy Jan 14 '15 at 18:08