C# найти все соответствующие элементы из списка

Допустим, у нас есть List<Product> и каждый элемент в списке имеет много List<Type>

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;}
}

после создания списка продуктов мне нужно сгруппировать их по типу, а затем найти все принадлежит каждому типу. Я думаю, я должен попробовать LINQ для этого. Вот что я сделал до сих пор, но, кажется, не правильный способ сделать эту работу. Может быть кто-то может мне помочь.

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);
                                }
                            }

тогда я пытаюсь искать вот так

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
}
}

4 ответов


следующий делает то, что вы хотите:

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) };

во-первых, вы делаете SelectMany чтобы получить список всех типов внутри продуктов. Для каждого типа вы помните идентификатор типа и соответствующий продукт:

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

каждого t теперь является анонимным объектом, содержащим идентификатор типа и продукт. Затем эти объекты группируются по типу id. Теперь у нас есть группа продуктов для каждого типа id.

чтобы дать вам пример, предположим, что у вас есть следующие продукты и типы:

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

The SelectMany дает следующий промежуточный результат:

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

мы группируем этот результат по типу id, поэтому получаем следующие группы:

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

и это результат, который вы хотели.


   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();

редактировать
Рональд Вильденберг правильно указал, что вызов Distinct() будет работать только в том случае, если экземпляры одинаковы. Чтобы исправить это, я обновляю следующую реализацию

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();      
    }
}

вы должны выбрать его ответ как правильный (хотя следующий тоже корректен)


чтобы узнать, сколько продуктов связано с каждым типом продукта (где продукт может иметь много типов), вы можете сначала выбрать все различные типы, как это

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

затем вы можете составить список всех продуктов, которые содержат отличные друг от друга типа:

 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();
 }

создайте свой компаратор равенства следующим образом:

 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();
    }
}

*отредактировано, чтобы добавить компараторы равенства


тогда я пытаюсь искать вот так

для этого вам не понадобится словарь...

код:

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);
    }

}

выход:

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