views:

70

answers:

4
public class thing
{
public int Id{get;set;}
public decimal shouldMatch1 {get;set;}
public int otherMatch2{get;set;}
public string doesntMatter{get;set;}
public int someotherdoesntMatter{get;set;}
}
List<thing> firstList = new List<thing>();
List<thing> secondList = new List<thing>();

firstList.Add( new thing{ Id=1,shouldMatch1 = 1.11M, otherMatch2=1000,doesntMatter="Some fancy string", someotherdoesntMatter=75868});
firstList.Add( new thing{ Id=2,shouldMatch1 = 2.22M, otherMatch2=2000,doesntMatter="Some fancy string", someotherdoesntMatter=65345});
firstList.Add( new thing{ Id=3,shouldMatch1 = 3.33M, otherMatch2=3000,doesntMatter="Some fancy string", someotherdoesntMatter=75998});
firstList.Add( new thing{ Id=4,shouldMatch1 = 4.44M, otherMatch2=4000,doesntMatter="Some fancy string", someotherdoesntMatter=12345});

secondList.Add( new thing{ Id=100,shouldMatch1 = 1.11M, otherMatch2=1000,doesntMatter="Some fancy string", someotherdoesntMatter=75868});
secondList.Add( new thing{ Id=200,shouldMatch1 = 2.22M, otherMatch2=200,doesntMatter="Some fancy string", someotherdoesntMatter=65345});
secondList.Add( new thing{ Id=300,shouldMatch1 = 3.33M, otherMatch2=300,doesntMatter="Some fancy string", someotherdoesntMatter=75998});
secondList.Add( new thing{ Id=400,shouldMatch1 = 4.44M, otherMatch2=4000,doesntMatter="Some fancy string", someotherdoesntMatter=12345});
//Select new firstList.Id,secondList.Id where firstList.shouldMatch1 ==secondList.shouldMatch1  && firstList.otherMatch2==secondList.otherMatch2

//SHould return 
//1,100
//4,400

Is there a way to intersect the lists, or must I iterate them?

Pseudocode

    firstList.Intersect(secondList).Where(firstList.shouldMatch1 == secondList.shouldMatch1 && firstList.otherMatch2 == secondList.otherMatch2)
Select new {Id1=firstList.Id,Id2=secondList.Id};

Regards

_Eric

+1  A: 

You need to make your thing type override Equals and GetHashCode to indicate its equality semantics:

public sealed class Thing : IEquatable<Thing>
{
    public int Id{get;set;}
    public decimal ShouldMatch1 {get;set;}
    public int OtherMatch2{get;set;}
    public string DoesntMatter{get;set;}
    public int SomeOtherDoesntMatter{get;set;}

    public override int GetHashCode()
    {
        int hash = 17;
        hash = hash * 31 + ShouldMatch1.GetHashCode() ;
        hash = hash * 31 + OtherMatch2.GetHashCode() ;
        return hash;
    }

    public override bool Equals(object other) {
        return Equals(other as Thing);
    }

    public bool Equals(Thing other) {
        if (other == null) {
            return false;
        }
        return ShouldMatch1 == other.ShouldMatch1 &&
               OtherMatch2 == other.OtherMatch2;
    }
}

Note that sealing the class makes the equality test simpler. Also note that if you put one of these in a dictionary as a key but then change Id, ShouldMatch1 or OtherMatch2 you won't be able to find it again...

Now if you're using a real anonymous type, you don't get to do this... and it's tricky to implement an IEqualityComparer<T> to pass to Intersect when it's anonymous. You could write an IntersectBy method, a bit like MoreLINQ's DisinctBy method... that's probably the cleanest approach if you're really using an anonymous type.

You'd use it like this:

var query = first.Intersect(second);

You then end up with an IEnumerable<Thing> which you can get the right bits out of.

Another option is to use a join:

var query = from left in first
            join right in second 
            on new { left.ShouldMatch1, left.OtherMatch2 } equals
               new { right.ShouldMatch1, right.OtherMatch2 }
            select new { left, right };

(EDIT: I've just noticed others have done a join too... ah well.)

Yet another option if you're only interested in the bits of the match is to project the sequences:

var query = first.Select(x => new { x.ShouldMatch1, x.OtherMatch2 })
                 .Intersect(second.Select(x => new { x.ShouldMatch1, 
                                                     x.OtherMatch2 }));
Jon Skeet
It could be that I am tired, but the implementation of `public override bool Equals` looks odd to me. I assume it's meant to be `return Equals(other as Thing);`.
Fredrik Mörk
@Fredrik: Yes indeed. Fixed.
Jon Skeet
Doesn't this return a Thing?
Eric
@Eric: Doesn't what exactly return a Thing? I've given various options...
Jon Skeet
@John, sorry, I was confused, I thought it returned a Thing but I wanted an Anon Type of the firstList.Id, secondList.Id
Eric
A: 

You will need an equality comparer:

public class thingEqualityComparer : IEqualityComparer<thing>
{
    #region IEqualityComparer<thing> Members

    public bool Equals(thing x, thing y) {
        return (x.shouldMatch1 == y.shouldMatch1 && x.otherMatch2 == y.otherMatch2) 

    public int GetHashCode(thing obj) {
            // if this does not suffice provide a better implementation.
        return  obj.GetHashCode();
    }

    #endregion
}

Then you can intersect the collections with:

firstList.Intersect(secondList, new thingEqualityComparer());

Alternatively, you can override the Equal function (see John's solution).

Also please not that thing is not anonymous class - this would be for example new { prop = 1 }.

Obalix
Yes, I misspoke on the title, I should have said Intersect/Join 2 lists to return a anon type ;)
Eric
+2  A: 

You could use an approach other than intersecting and implementing an IEqualityComparer, as follows:

var query = from f in firstList
            from s in secondList
            where f.shouldMatch1 == s.shouldMatch1 &&
                  f.otherMatch2 == s.otherMatch2
            select new { FirstId = f.Id, SecondId = s.Id };

foreach (var item in query)
    Console.WriteLine("{0}, {1}", item.FirstId, item.SecondId);

This is essentially the Enumerable.SelectMany method in query format. A join would likely be quicker than this approach.

Ahmad Mageed
Thanks, this is what I was trying to do
Eric
+2  A: 

Consider using a multi-condition join to join your records. An intersect would cause you to lose ID's either on the left or the right.

Here is an example of a working multi-column join for this particular scenario. The appeal of this query is that it requires no equality comparer, and it allows you to retrieve the ID column while joining on the other specified columns.

var query = from first in firstList
            join second in secondList on
                new { first.shouldMatch1, first.otherMatch2 }
                    equals
                new { second.shouldMatch1, second.otherMatch2 }
            select new
            {
                FirstId = first.Id,
                SecondId = second.Id
            };
kbrimington
+1 for the join equivalent of my response. I was going for this originally :)
Ahmad Mageed