3
public class Item
{
    public int Id {get; set;}
    public bool Selected {get; set;}
}

List<Item> itemList = new List<Item>(){ /* fill with items */ };

I need to create a list of Items that meet the following criteria. From itemList, I need to group the items by Id and then choose a single item from each group. The chosen item must be one in which Selected == true. If no item in the group is selected, then any item can be chosen, it doesn't matter, but one must be chosen.

Based on this question: How to get distinct instance from a list by Lambda or LINQ

I was able to put together the following, which seems to work:

var distinctList = itemList.GroupBy(x => x.Id, 
    (key, group) => group.Any(x => x.Selected) ? 
        group.First(x => x.Selected) : group.First());

Is there a more efficient or simpler way to achieve this? I tried FirstOrDefault() but couldn't seem to make it do what I needed. My concern with the efficiency in the above code, is the call to Any().

Community
  • 1
  • 1
J. Andrew Laughlin
  • 1,926
  • 3
  • 21
  • 33
  • Is that not simple enough for you? Add in a few extra line breaks and/or remove some of the indentation so it fits on the screen better and it looks fine to me. – Servy Apr 16 '12 at 20:48
  • @Servy -- I don't understand the down vote? The answers below are simpler and more efficient than my example above. That's why I asked the question. – J. Andrew Laughlin Apr 17 '12 at 01:08

3 Answers3

3

You can indeed use the FirstOrDefault extension method, but use the version that takes a predicate, and combine it with the coalesce operator (??) like so (I've used query syntax here to make it easier):

var distinctList =
    from item in itemList
    group item by item.Id into g
    select g.FirstOrDefault(i => i.Selected) ?? g.First();

Using the predicate in FirstOrDefault, you are picking the first item where the Selected property is true. If there is none, then assuming your type is a reference type (and this is important this to work with FirstOrDefault), it will return null.

If null is returned, then you'll just return the first item in the group (through the call to First), since any item can be returned and a group can't exist without items in it (so the call to First is always guaranteed to succeed).

Basically, you are applying a selector to the result of the grouping, not while grouping.

casperOne
  • 73,706
  • 19
  • 184
  • 253
0

You could employ the fact that bool implements IComparable<bool>, with true being greater than false:

var distinctList = itemList.GroupBy(x => x.Id,  
    (key, group) => group.OrderByDescending(x => x.IsSelected).First()); 

This requires enumerating and sorting the whole group, though, so your performance will be better if you stick with your approach.

For groups with a selected item, your approach requires iterating the list twice up to the point of the first selected item. You could remove that duplication like this:

var distinctList = itemList.GroupBy(x => x.Id,  
    (key, group) => group.FirstOrDefault(x => x.Selected) ?? group.First()); 
phoog
  • 42,068
  • 6
  • 79
  • 117
0

Simpler answer here

You can use

groupedSource.SelectMany(group => group).Single(item => item.Id == 1)
Community
  • 1
  • 1
Loul G.
  • 997
  • 13
  • 27