tags:

views:

47

answers:

5

I have 2 Lists.

var adultList = new List<Dude>();
adultList.Add(new Dude() { ID = 2, Name = "Randy Marsh" });
adultList.Add(new Dude() { ID = 3, Name = "Jimbo Kern" }); // no kids
adultList.Add(new Dude() { ID = 4, Name = "Gerald Broflovski" });
adultList.Add(new Dude() { ID = 5, Name = "Stuart McCormick" });
adultList.Add(new Dude() { ID = 6, Name = "Liane Cartman" });
adultList.Add(new Dude() { ID = 7, Name = "Ned Gerblansky" }); // no kids

var childList = new List<Dude>();
childList.Add(new Dude() { ID = 8, Name = "Stan Marsh", ParentID = 2 });
childList.Add(new Dude() { ID = 9, Name = "Kyle Broflovski", ParentID = 4 });
childList.Add(new Dude() { ID = 10, Name = "Ike Broflovski", ParentID = 4 });
childList.Add(new Dude() { ID = 11, Name = "Kenny McCormick", ParentID = 5 });
childList.Add(new Dude() { ID = 12, Name = "Eric Cartman", ParentID = 6 });

I want a Linq query to return that returns any Dudes in the adultList that do NOT have any kids. The result list should also have no null entries (in sample above, should have a Count() of 2 and return only Jimbo and Ned).

var nullList = new List<Dude>();
nullList.Add(null);
var adultsWithNoChildren = adultList.GroupJoin(
  childList,
  p => p.ID,
  c => c.ParentID,
  (p, c) =>
  {
    if (c.FirstOrDefault() == null) return p;
    return null;
  })
  .Except(nullList);

Is this the best way to accomplish this? is there another Linq function or something else? I don't like the idea of having the nullList created, but that is the only ensure that the result list has an accurate count.

Thanks

+1  A: 
adultList.Where(x => !childList.Select(y => y.ParentID).Contains(x.ID));
Jake
+2  A: 

My approach would be like this:

var adultNoChildren = (from adult in adultList
                   where !childList.Any(child => child.ParentID == adult.ID)
                   select adult).ToList();

This can also be done using the other LINQ Syntax, but I can never remember that :) (The nice thing about .Any is that it stops as soon as it finds a result, so the entire child list is only traversed for adults with no children)

Michael Stum
thanks for all the responses (Anthony, Jake, LukeH). This is the best (minus the ToList) because of the use of the Any, which is probably better for performance.
Andre
+1  A: 
var noKids = from adult in adultList
                join child in childList
                on adult.ID equals child.ParentID into g
                from item in g.DefaultIfEmpty()
                where item == null
                select adult;
Anthony Pegram
+1  A: 
var noKids = adultList.Where(a => !childList.Any(c => c.ParentID == a.ID));
LukeH
Essentially just a translation of Michael Stum's answer from query comprehension syntax to fluent extension method syntax.
LukeH
+1  A: 

If the lists have any size at all, I have to recommend a solution involving GroupJoin, due to hashjoin costing n+m, versus where!any costing n*m

IEnumerable<Dude> noKids =
  from adult in adultList
  join child in childList
  on adult.ID equals child.ParentID into kids
  where !kids.Any()
  select adult;

Or in method form

IEnumerable<Dude> noKids = adultList.GroupJoin(
     childList,
     adult => adult.ID,
     child => child.ParentID,
     (adult, kids) => new {Dude = adult, AnyKids = kids.Any() })
  .Where(x => !x.AnyKids)
  .Select(x => x.Dude);

Lastly, is Liane really a dude?

David B