4

Ok, so I have two lists of items which are of different types.

var whales = new List<Whale>();

var crabs = new List<Crab>();

So they both have the id property. So save the lists have objects with Ids of:

whales: 1, 3, 4, 5 crabs: 1, 2, 3, 4

Ok so I have a query:

var matchedPairs = from c in crabs
                   from w in whales
                   where c.Id = w.Id
                   select new { crab = c, whale = w };

So that works fine for getting the matches. Where I'm having trouble is I want to get a list of crabs that don't have a matching whale ie. Crab Id = 2. Then I want to get the whales that don't have a matching crab ie Whale Id = 5.

Can anyone tell me how to write these queries?

Yuriy Faktorovich
  • 67,283
  • 14
  • 105
  • 142
AnonyMouse
  • 18,108
  • 26
  • 79
  • 131

7 Answers7

12

if you want to select only crabs.

var result = crabs.Where(c => whales.All(w => w.Id != c.Id));
Dmitry Khryukin
  • 6,408
  • 7
  • 36
  • 58
4

Maybe something like this:

var unmatchedCrabs = from c in crabs
                     where !whales.Any(w => w.Id == c.Id)
                     select c;
Glen Hughes
  • 4,712
  • 2
  • 20
  • 25
  • This is a good answer but it turned out my crabs were of type IEnumerable. Didn't know that at the time so didn't mention it. There is no Exists on this so couldn't get that to work. – AnonyMouse Aug 01 '12 at 03:52
  • Ok... in that case, there's an Any method that would work on IEnumerable the same way. I've updated my answer to reflect this. – Glen Hughes Aug 01 '12 at 03:53
2

You want an outer join:

var crabsWithoutWhales = from c in crabs
                         join w1 in whales on c.Id equals w1.Id into ws
                         from w2 in ws.DefaultIfEmpty()
                         where w2 == null
                         select c;
Andrew Kennan
  • 13,947
  • 3
  • 24
  • 33
1

You can use Union operation from set operations.

To use it you will have to override default Equality comparer and GetHashCode method. Once you have those then you can do something like this:

var matchedPair = crabs.Union(whales);

In your case, you should have a base class; e.g. Animals with Equality comparer. Another option would be to implement IEqualityComparer<>

Community
  • 1
  • 1
loopedcode
  • 4,863
  • 1
  • 21
  • 21
  • Union requires both Enumerables to be of the same type. The OP want something that will compare lists of different types. – Glen Hughes Aug 01 '12 at 02:58
  • Yes, but he can use a base class with Equality comparer or implement IEqualityComapere, i.e. custom comparer. – loopedcode Aug 01 '12 at 03:00
  • True, but creating a base class may not be relevant to the business scenario... adding a base class only for this operation is a poor design. – Glen Hughes Aug 01 '12 at 03:06
  • The result of Union is not desired anyway... Union returns all items from crabs, even if they exist in whales. – Glen Hughes Aug 01 '12 at 03:14
0
var result = crabs.SelectMany(c => whales, (c, w) => new { c, w })
                              .Where(@t => whales.All(x => x.Id != t.c.Id) && crabs.All(x => x.Id != t.w.Id))
                              .Select(@t => new {crab = @t.c, whale = @t.w});
Dmitry Khryukin
  • 6,408
  • 7
  • 36
  • 58
0

Here's the neatest looking LINQ that I can think of to do what you need:

var whalesOnly =
    from w in whales
    join c in crabs on w.Id equals c.Id into gcs
    where !gcs.Any()
    select w;

var crabsOnly =
    from c in crabs
    join w in whales on c.Id equals w.Id into gws
    where !gws.Any()
    select c;

How do these look for you?

BTW, you can do your join query a little nicer like this:

var whalesAndCrabs =
    from whale in whales
    join crab in crabs on whale.Id equals crab.Id
    select new { crab, whale };
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
0

You need two Left Join by GroupJoin method like this:

var result1 = whales.GroupJoin(crabs, w => w.ID, c => c.ID, (w,cs) => new {WhaleID = w.ID, Matches = cs});
var result2 = crabs.GroupJoin(whales, c => c.ID, w => w.ID, (c, ws) => new {CrabID = c.ID, Matches = ws});

Then, filter the result by what you want.