views:

107

answers:

5

I've been using LINQ for a while now, but seem to be stuck on something with regards to Unique items, I have the folling list:

List<Stock> stock = new List<Stock>();

This has the following Properties: string ID , string Type, string Description, example:

public class Stock
{
    public string ID { get; set; }
    public string Type { get; set; }
    public string Description { get; set; }
}

I want to have a LINQ query that will group the items in Stock by their Type and return this as a new List of Stock (it has to be the same type as the original Stock List).

Example Data:

 ID   Type                 Description
----------------------------------------------
  1   Kitchen Appliance    Dishwasher  
  2   Living Room          Television  
  3   Kitchen Appliance    Washing Machine  
  4   Kitchen Appliance    Fridge  

...

My Linq query wants to be able to return all the Kitchen Appliances for Example.
So I would pass this as a "type" into the query and it would return the items 1, 3 and 4 from this example list.

This list returned must also be of type: List<Stock>.

Essentially I want a list of the unique items by type, kind of like an SQL Distinct query, how do I achieve this in LINQ?
Alternative solutions are fine but must be Silverlight / C# client code only.

Just another clarification is that I also may not provide the parameter "Kitchen Appliance" and may just want the unique ones, for example It would return Kitchen Appliance and Living Room once each only to kind of like a category no matter how many of that Type there are.

+2  A: 
static class EnumerableEx
{
    // Selectively skip some elements from the input sequence based on their key uniqueness.
    // If several elements share the same key value, skip all but the 1-st one.
    public static IEnumerable<tSource> uniqueBy<tSource, tKey>( this IEnumerable<tSource> src, Func<tSource, tKey> keySelecta )
    {
        HashSet<tKey> res = new HashSet<tKey>();
        foreach( tSource e in src )
        {
            tKey k = keySelecta( e );
            if( res.Contains( k ) )
                continue;
            res.Add( k );
            yield return e;
        }
    }
}

// Then later in the code
List<Stock> res = src.uniqueBy( elt => elt.Type ).ToList()
Soonts
What? The OP wants to be able to get, e.g., all kitchen appliances. The code you've provided would create a list with exactly one kitchen appliance... and one of each other type. This doesn't seem to be accomplishing the OP's goal.
Dan Tao
True, this won't accomplish the goal. Granted, the OP used words that really didn't fit to what was actually being sought.
p.campbell
@p.campbell: That's true. The last part of the question in particular seems to be confusingly worded.
Dan Tao
I wasn't sure what I needed - I was stuck on the problem was unsure how to describe what I needed, but I got half what I needed right away - so it was just the grouping by similar type bit I needed - which I did not realise until after asking.
RoguePlanetoid
+7  A: 

Flexible approach

Use GroupBy and ToDictionary to create a dictionary of List<Stock> values keyed on the Type property:

var appliancesByType = stock
    .GroupBy(item => item.Type)
    .ToDictionary(grp => grp.Key, grp => grp.ToList());

Then you can access the types themselves as well as a list for any given type quite easily:

// List of unique type names only
List<string> stockTypes = appliancesByType.Keys.ToList();

// Or: list of one stock item per type
List<Stock> exampleStocks = appliancesByType
    .Select(kvp => kvp.Value[0])
    .ToList();

// List of stock items for a given type
List<Stock> kitchenAppliances = appliancesByType["Kitchen Appliance"];

This approach really takes care of all your needs, as I see it. But for some other options, see below.

Alternate (quick & dirty) approach

You can always just use Where to get the items of the type you want, then ToList to put these items in a new List<Stock>:

List<Stock> kitchenAppliances = stock
    .Where(item => item.Type == "Kitchen Appliance")
    .ToList();

In response to this last part:

Just another clarification is that I also may not provide the parameter "Kitchen Appliance" and may just want the unique ones, for example It would return Kitchen Appliance and Living Room once each only to kind of like a category no matter how many of that Type there are.

Here, you seem to be wanting something completely different: basically the behavior provided by Distinct. For this functionality, you could essentially go with Soonts's answer (optionally, returning an IEnumerable<tKey> instead of IEnumerable<tSource>), or you could just leverage Distinct in combination with Select to avoid the need to implement an IEqualityComparer<Stock> (see below).


Update

In response to your clarification, here's my recommendation: two methods, one for each purpose (Single Responsibility Principle):

// This will return a list of all Stock objects having the specified Type
static List<Stock> GetItemsForType(string type)
{
    return stock
        .Where(item => item.Type == type)
        .ToList();
}

// This will return a list of the names of all Type values (no duplicates)
static List<string> GetStockTypes()
{
    return stock
        .Select(item => item.Type)
        .Distinct()
        .ToList();
}
Dan Tao
This works fine if I provide a type, but I forgot to say (and have edited my question) that if no type is provided I want the list to be one each of the items "Kitchen Appliance" and "Living Room" - but this is still useful to me. It doesn't matter which one of the items is selected as for this use the Type will be used whether it is unique or not.
RoguePlanetoid
@RoguePlanetoid: So you want basically for the type parameter to be optional -- if it's specified, all items of that type are returned; otherwise, exactly one item for each type is returned? This is an odd API in my opinion; I'd recommend implementing this functionality in the form of two separate methods (e.g., `GetStockForType` and `GetStockTypes`).
Dan Tao
I had a feeling I needed to to a comparisor to make this work, the solutions here are fine for when I provide the type, but If I can compare one type with another to get unique ones, this will be fine, just need to get it into a LINQ expression.Two methods will be fine as they'll be used in different places.
RoguePlanetoid
@RoguePlanetoid: For the second function you're describing (list of unique types) I think `Distinct` is all you need. See my update.
Dan Tao
Thanks for the update, the second part gets me the list of unique types - all I need is one of the Stock items for that type so the function returns the same type. Thanks, marking as answer.
RoguePlanetoid
Just got this to work in my code now - thanks again! Works great, exactly what I wanted - sorry about being so vague previously!
RoguePlanetoid
+2  A: 
var kitchenAppliances = stocks.Where(stock => stock.ID == "Kitchen Appliance");
Martin Liversage
You used ID instead of Type though :)
Lasse V. Karlsen
+1  A: 

I probably don't understand the question, but this sounds to me like a pretty simple Linq-query:

List<Stock> stock = new List<Stock>();
... populate your list as per your example

List<Stock> kitchenAppliances =
    (from obj in stock
     where obj.Type == "Kitchen Appliance"
     select obj).ToList();

or if you prefer the extension-method syntax:

List<Stock> kitchenAppliances =
    stock.Where(obj => obj.Type == "Kitchen Appliance").ToList();

What I don't understand is your usage of distinct and unique in this context. The objects are already unique, and it seems to me that you want a basic query "give me all kitchen appliances".

Lasse V. Karlsen
+1  A: 

Sounds like it's a simple Where clause needed.

List<Stock> kitchen= stock.Where(s=>s.Type=="Kitchen Appliance")
                          .OrderBy(s=>s.Description).ToList();

If you wanted strictly the Types contained in the source list:

string[] typesFound = stock.Select(s=>s.Type).Distinct().ToArray();

p.campbell