在C#中从一个List<Item>中查找所有匹配的项

9
假设我们有一个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个回答

8
以下内容可以实现您想要的功能:
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 获取产品中所有类型的列表。对于每种类型,您需要记住类型 ID 和相应的产品:

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

每个 t 现在都是一个匿名对象,包含类型 id 和产品。接下来,您将这些对象按类型 id 分组。现在我们有每个类型 id 的一组产品。
举个例子,假设您有以下产品和类型:
Product A -- Types 1, 2, 3
Product B -- Types 1
Product C -- Types 1, 3
会产生以下中间结果:
1, A
2, A
3, A
1, B
1, C
3, C

我们将这个结果按类型ID分组,得到以下几组:
1, { A, B, C }
2, { A }
3, { A, C }

这就是你想要的结果。


Wildenburg - 你的答案也是正确的。但我最初看到 @Franchesca 建议的答案,并在我的代码中实现了它。感谢大家的帮助。我也会将这个答案标记为解决方案。(希望可以这样做) - randika
@randika 很高兴我们能够帮到你 :) 你只能标记一个答案,但我认为Ronald可能因为添加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();

编辑
Ronald Wildenberg正确指出,对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();      
    }
}

你应该选择他的答案作为正确答案(虽然下一个答案也是正确的)

0

要找出每个产品类型关联的产品数量(一个产品可以有多个类型),您可以首先像这样选择所有不同的类型

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

*编辑以添加相等比较器


1
只有当不同产品的“Type”对象引用相同实例时,对“Distinct”的调用才有效。如果不是这种情况,则所有“Type”对象都是不同的。可以通过让“Type”实现“IEquatable<Type>”或将“IEqualityComparer<Type>”的实现传递给“Distinct()”来解决此问题。 - Ronald Wildenberg
@Franchesca - 看起来选择不同的类型没有起作用。 - randika
那么你需要按照Ronald Wildenberg的建议去做,为你的产品类型创建一个IEqualityComparer<Type>。我会更新我的答案来包含这一点。 - Franchesca
@Franchesca - 我尝试了你建议的方法,它对我很有效。此外,我对你的ProductTypeEqualityComparer类进行了一些修改,使其继承自IEqualityComparer<Type>(可能还不是所有人都能看到)。 - randika

-1
然后我尝试这样搜索。
对此你不需要字典...
代码:
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

你可能不一定需要一个字典,但是将信息组织成某种集合形式以便在程序的其他地方使用会很好。问题的提问者没有明确说明他们将用这些信息做什么,我认为将其写到控制台上并不特别有用 :) - Franchesca
Francesca我已经更新了上面的代码并加上了解释。而且OP在问题的结尾说明了它想要如何使用这些信息。我不会对Console.WriteLine部分进行评论。实际上,Console.WriteLine可能是其他东西:=) - Marino Šimić
顺便说一下,IQueryable<Type> 是某种集合,每当您在其上编写 ToList() 时,它都可以在程序的其他地方传递,并且列表的创建可以延迟到需要的位置。如果那些给出 -1 的人发现自己有权这样做,解释一下会很好。 - Marino Šimić

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接