2011-07-03 6 views
9

Powiedzmy mamy List<Product> i każda pozycja produktu na liście ma wiele List<Type>C# znaleźć wszystkie pasujące przedmioty z listy <Item>

public class Product{ 
public int Id {get:set;} 
public string Name {get:set;} 
public List<Type> Types {get;set;} 
} 

public class Type{ 
public int Id {get;set;} 
public string Name{get;set;} 
} 

Po utworzyć listę produktów, muszę grupy je przez typ a następnie znaleźć wszystkie należy do każdego typu. Myślę, że powinienem wypróbować pod tym kątem LINQ. Oto, co zrobiłem do tej pory, ale wydaje się, że nie jest to właściwy sposób na wykonanie tej pracy. Być może ktoś mi pomoże.

var productsList = new List<Product>(); 
//Adding products and types for each of them 

var productTypesList = new Dictionary<int, string>(); 

foreach (var p in productsList) 
          { 
           var pTypes = p.Types; 
           foreach (var ptype in 
            pTypes.Where(x=> !productTypesList .ContainsKey(x.Id))) 
           { 
            productTypesList.Add(ptype.Id, ptype.Name); 
           } 
          } 

Wtedy staram się szukać jak to

foreach (var t in productTypesList) 
{ 
var matches = productsList.FindAll(........); 
// from here I need to find all the product which belongs to type (t.id) 

if (matches.Count > 0) 
{ 
//Do somthing here 
} 
} 

Odpowiedz

8

następujących robi to, co chcesz:

var productsPerType = 
    from t in products.SelectMany(
     p => p.Types, (p, t) => new { Product = p, TypeId = t.Id }) 
    group t by t.TypeId 
    into g 
    select new { g.Key, Products = g.Select(x => x.Product) }; 

Najpierw robisz SelectMany do uzyskać listę wszystkich typów wewnątrz produktów. Dla każdego typu pamiętasz identyfikatora typu i odpowiedni produkt:

from t in products.SelectMany(
    p => p.Types, (p, t) => new { Product = p, TypeId = t.Id }) 

Każdy t jest teraz anonimowy obiekt zawierający identyfikator typu oraz produkt. Następnie grupujesz te obiekty według identyfikatora typu. Teraz mamy grupę produktów dla każdego identyfikatora typu.

Aby dać przykład, załóżmy, że masz następujące produkty i rodzaje:

Product A -- Types 1, 2, 3 
Product B -- Types 1 
Product C -- Types 1, 3 

SelectMany daje następujący wynik pośredni:

1, A 
2, A 
3, A 
1, B 
1, C 
3, C 

grupujemy wynik ten typ id więc uzyskać następujące grupy:

1, { A, B, C } 
2, { A } 
3, { A, C } 

I to jest r którego chciałeś.

+0

Wildenburg - Twoja odpowiedź również jest prawidłowa. Ale najpierw zobaczyłem odpowiedź zasugerowaną przez @Franchesca i zaimplementowałem ją w moim kodzie. Dzięki za pomoc. Zaznaczę to również jako odpowiedź. (mam nadzieję, że to możliwe) – randika

+0

@randika Cieszę się, że mogliśmy pomóc :) Możesz zaznaczyć tylko jedną odpowiedź, ale myślę, że Ronald prawdopodobnie zasługuje na to, aby dodać rzeczy związane z IEqualityComparer. – Franchesca

2
var types = (from p in productsList 
       from t in p.Types 
       select t).Distinct(new TypeComparerById()); 
    var productsGrouped = (from t in types 
         select new 
         { 
          Type = t, 
          ProductsPerType = productsList.Where(p=>p.Types.Any(pt=>pt.Id == t.Id)) 
         }).ToList(); 

Edycja
Ronald Wildenberg słusznie wskazał, że wezwanie na Wyraźny() będzie działać tylko wtedy, gdy przypadki są takie same. Aby poprawić tę aktualizację I z następującymi realizacji

public class TypeComparerById : IEqualityComparer<Type> 
{ 
    public bool Equals(Type t1, Type t2) 
    { 
     if (t1.Id == t2.Id) 
     { 
      return true; 
     } 
     else 
     { 
      return false; 
     } 
    } 

    public int GetHashCode(Type t) 
    { 
     return t.Id.GetHashCode();  
    } 
} 

należy wybrać swoją odpowiedź jako poprawna (choć obok nich jest poprawna zbyt)

0

Aby dowiedzieć ile produkty są związane z każdego rodzaju produktu (gdzie produkt może mieć wiele rodzajów), można najpierw wybrać wszystkie odrębnych typów jak ten

var productTypeEqualityComparer = new ProductTypeEqualityComparer(); 
var results = productsList.SelectMany(b => b.Types).Distinct(productTypeEqualityComparer); 

wtedy można zrobić listę wszystkich produktów, które zawierają każdy odrębny typ:

Dictionary<Type, List<Product>> productsByProductType = new Dictionary<Type, List<Product>>() 
foreach (Type productType in results) 
{ 
     productsByProductType[productType] = productsList.Where(p => p.Types.Contains(productType, productTypeEqualityComparer)).ToList(); 
} 

Stwórz comparer równość tak:

public class ProductTypeEqualityComparer : IEqualityComparer<Type> 
{ 
    public bool Equals(Type x, Type y) { 
// I'm assuming here that the ID is unique to each type, but if it is 
     return x.Id == y.Id; 
    } 

    public int GetHashCode(Type obj) { 
     return obj.Id.GetHashCode(); 
    } 
} 

* Edytowane dodać równości porównywarki

+1

Połączenie z 'Wyróżnione' na Działa tylko wtedy, gdy obiekty 'Type' z różnych produktów odnoszą się do tych samych instancji. Jeśli tak nie jest, wszystkie obiekty 'Type' są różne. Można to naprawić, wprowadzając 'Type' implement 'IEquatable ' lub przekazując implementację 'IEqualityComparer ' do 'Distinct()'. –

+0

@Franchesca - wydaje się, że wybieranie różnych typów nie działa. – randika

+0

Następnie należy zrobić to, co sugeruje Ronald Wildenberg, i utworzyć produkt IEqualityComparer dla danego typu produktu. Zaktualizuję moją odpowiedź, aby to uwzględnić. – Franchesca

-1

Wtedy staram się szukać jak to

za to nie trzeba słownika ...

Kod:

var productsList = new List<Product>(); 
productsList.Add(new Product { Id = 1, Name = "p1", Types = new List<Type>() { new Type() { Id = 1, Name = "ptype1" }, new Type() { Id = 2, Name = "ptype2" } } }); 
productsList.Add(new Product { Id = 2, Name = "p2", Types = new List<Type>() { new Type() { Id = 1, Name = "ptype1" } } }); 
productsList.Add(new Product { Id = 3, Name = "p3", Types = new List<Type>() { new Type() { Id = 2, Name = "ptype2" } } }); 
productsList.Add(new Product { Id = 4, Name = "p4", Types = new List<Type>() { new Type() { Id = 2, Name = "ptype2" }, new Type() { Id = 3, Name = "type3" } } }); 

// this is an IEnumerable<Type> (types with the same Id and different name will take only the first) 
var productTypesList = (from p in productsList  // for each product 
         from t in p.Types   // for each type in product 
         group t by t.Id into types // group em by Id into types 
         select types.First());  // but use only the first (else this would be an IEnumerable<IGrouping<Type>> 

Console.WriteLine("Types:"); 

//EDIT: Since Francesca had some complains, and thought having a dictionary from this is difficult, here is a one liner to do that. 
// This can be done by surrounding the query above with parenthesis and adding the ToDictionary() call at the end 
// I prefer not to use a dictionary unless needed and your code seems not to need it since you need to loop on product types, as stated at the end of the question 
// Use this only if you need to store or pass around these values. if you do, you loose potential other properties of your types. 
var prodTypeDict = productTypesList.ToDictionary(v => v.Id, v => v.Name); 

foreach (var p in productTypesList) 
{ 
    Console.WriteLine(p.Id + " " + p.Name); 
} 

foreach (var type in productTypesList) 
{ 
    // this is an IEnumerable<Product> 
    var products = from p in productsList     // for each product 
        where p.Types.Any(t => t.Id == type.Id) // that has this type 
        select p; 

    Console.WriteLine("Products of type: " + type.Name); 
    foreach (var p in products) 
    { 
     Console.WriteLine(p.Id + " " + p.Name); 
    } 

} 

wyjściowa:

Types: 
1 ptype1 
2 ptype2 
3 type3 
Products of type: ptype1 
1 p1 
2 p2 
Products of type: ptype2 
1 p1 
3 p3 
4 p4 
Products of type: type3 
4 p4 
+0

Nie musisz specjalnie potrzebować słownika, ale dobrze jest zorganizować informacje w jakiejś kolekcji, gdzie można go użyć w innym miejscu w programie. Autor pytania nie sprecyzował, do czego ma zamiar użyć tej informacji, i nie sądzę, że napisanie jej na konsolę jest szczególnie przydatne :) – Franchesca

+0

Francesca Zaktualizowałem powyższy kod z wyjaśnieniem. A operacja opowiedziała, w jaki sposób chce wykorzystać informacje na końcu pytania. Nie będę komentować części Console.WriteLine. Console.WriteLine może w rzeczywistości być czymś jeszcze: =) –

+0

A przy okazji IQueryable jest jakąś kolekcją za każdym razem, gdy napiszesz ToList() na niej, plus może być przekazana gdzieś indziej w programie, a tworzenie listy może być opóźnione w razie potrzeby. Byłoby miło, gdyby ci, którzy dają -1, wyjaśnili, czy są w tym słuszni. –

Powiązane problemy