【问题标题】:C# find all matching Items from a List<Item>C# 从 List<Item> 中查找所有匹配项
【发布时间】:2011-07-03 08:51:58
【问题描述】:

假设我们有一个List&lt;Product&gt;,并且列表中的每个产品项都有很多List&lt;Type&gt;

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

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    以下是你想要的:

    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
    

    SelectMany 给出以下中间结果:

    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 很高兴我们能提供帮助 :) 您只能标记一个答案,但我认为 Ronald 可能应该得到它添加 IEqualityComparer 的东西。
    【解决方案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();      
        }
    }
    

    你应该选择他的答案是正确的(尽管下一个也是正确的)

    【讨论】:

      【解决方案3】:

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

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

      *编辑添加相等比较器

      【讨论】:

      • 只有当来自不同产品的Type 对象引用相同的实例时,对Distinct 的调用才有效。如果不是这种情况,则所有Type 对象都是不同的。这可以通过让Type 实现IEquatable&lt;Type&gt; 或通过将IEqualityComparer&lt;Type&gt; 的实现传递给Distinct() 来解决。
      • @Franchesca - 似乎选择不同的类型不起作用。
      • 那么您需要按照 Ronald Wildenberg 的建议进行操作,并为您的产品类型创建一个 IEqualityComparer。我将更新我的答案以包括这一点。
      • @Franchesca - 我尝试了你的建议,它对我有用。另外,我对您的 ProductTypeEqualityComparer 类进行了一些修改,以从 IEqualityComparer 继承(可能尚未对所有人可见)
      【解决方案4】:

      那我试着像这样搜索

      为此,您不需要字典...

      代码:

      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
      

      【讨论】:

      • 您可能并不特别需要字典,但最好将信息组织在某种集合中,以便在程序的其他地方使用它。问题的作者没有说明他们将使用该信息做什么,我认为将其写到控制台并不是特别有用:)
      • Francesca 我用解释更新了上面的代码。并且操作人员在问题的末尾说明了它希望如何使用这些信息。我不会评论 Console.WriteLine 部分。 Console.WriteLine 实际上可能是别的东西 :=)
      • 顺便说一下,IQueryable 是你在上面写 ToList() 时的某种集合,而且它可以在程序的其他地方传递,并且可以延迟列表的创建到需要的地方。如果那些给出-1的人发现自己这样做是正确的,那么他们会很好地解释自己。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-31
      • 1970-01-01
      • 2017-11-14
      • 2021-10-17
      • 2013-12-19
      • 2012-02-23
      • 2022-07-22
      相关资源
      最近更新 更多