使用LINQ查找共同组件

4
我将尝试查找所有具有共同组件的订单,以及共同组件的列表:
组件类:
public class Component
{
    public Component(string name)
    {
        this.Name = name;
    }
    public string Name { get; private set; }
}

订单类:

internal class Order
{
    public Order(string name,List<Component> components)
    {
        this.Name = name;
        this.Components = components;
    }
    public string Name { get; private set; }

    public List<Component>Components { get; private set; }
}

例如:

var component1 = new Component("B1");
var component2 = new Component("B2");
var component3 = new Component("B3");
var component4 = new Component("B4");

var order1 = new Order("M1", new List<Component> { component2 });
var order2 = new Order("M2", new List<Component> { component1, component2, component3, component4 });
var order3 = new Order("M3", new List<Component> { component1, component2 });

var dependents = from cmp1 in order1.Components
                 join cmp2 in order2.Components
                 on cmp1.Name equals cmp2.Name
                 select new
                 {
                     order1Name = order1.Name,
                     order2Name = order2.Name,
                     ComponentName = cmp1.Name
                 };

var result = dependents.ToList();

结果显示了正确的信息,即订单1和订单2之间的共同组件是component2(“B2”)。
如何使用订单列表使其更加普遍:
    var orders = new List<Order> { order1, order2, order3 };

我希望每2个订单都能得到一个共同组件的列表,而不是为每个可能的配对做一次。

我推测这应该是类似于:

var allDependents = 
runs.ForEach(order=>order.Components)                
....
from cmp1 in order1.Components
join cmp2 in order2.Components
on cmp1.Name equals cmp2.Name
select new
{
    order1Name = order1.Name,
    order2Name = order2.Name,
    ComponentName = cmp1.Name
};

附加信息:

如下图所示,我们可以看到每2个订单对应一个组件列表

enter image description here


orders.SelectMany(col=>col.Components, (o,co)=> new {O=o.Name, C=co.Name}) .GroupBy(key=>key.C) .Select(kv=> new {K=kv.Key, L=string.Join(",",kv.Select(kvv=>kvv.O))}) - Helic
5个回答

4
假设所有名称都是唯一的,您可以这样做。
var results = from o1 in orders
              from c1 in o1.Components
              from o2 in orders.SkipWhile(o => o.Name != o1.Name)
              from c2 in o2.Components
              where o1.Name != o2.Name && c1.Name == c2.Name
              select new 
              {  
                  Order1 = o1.Name,   
                  Order2 = o2.Name,   
                  Component = c1.Name   
              };

foreach(var r in results) Console.WriteLine(r);

它产生以下输出

{ 订单1 = M1,订单2 = M2,组件 = B2 }

{ 订单1 = M1,订单2 = M3,组件 = B2 }

{ 订单1 = M2,订单2 = M3,组件 = B1 }

{ 订单1 = M2,订单2 = M3,组件 = B2 }


很好,如果我需要得到以下结果: { Order1 = M1,Order2 = M2,Component = B2 } { Order1 = M1,Order2 = M3,Component = B2 } { Order1 = M2,Order2 = M3,Component = B1,B2 },我需要一个新的LINQ并执行分组操作,对吗? - ehh
笛卡尔积。O(N^2)。但是当然(典型的LINQ标签)没人关心。 - Ivan Stoev

4

一种可能有效的方法是使用带有额外条件的半连接,如下所示:

var orders = new List<Order> { order1, order2, order3 };
var orderComponents = from order in orders
         from component in order.Components
         select new { order, component };
var dependents =
    from e1 in orderComponents
    join e2 in orderComponents on e1.component.Name equals e2.component.Name
    where e2.order != e1.order && e2.order.Name.CompareTo(e1.order.Name) > 0
    select new
    {
        order1Name = e1.order.Name,
        order2Name = e2.order.Name,
        ComponentName = e1.component.Name
    };

唯一需要提到的细节是标准e2.order != e1.order && e2.order.Name.CompareTo(e1.order.Name) > 0。第一个条件从左侧排除了订单,而第二个条件排除了像{ M2,M1 }这样的重复项。


1

我更熟悉流畅的Linq语法,其中你可以按照自己的意愿进行操作:

var orders = new[] { order1, order2, order3 };

var dependents = orders.SelectMany(order =>
        orders
        .Where(other => other.Name != order.Name)
        .SelectMany(other => other.Components.Intersect(order.Components)
        .Select(c => new { order, other, component = c }))
    ).ToList();

1
那会产生重复。基本上,它将匹配M1中的B2作为“order”与M2作为“other”,然后将它们与M1作为“other”和M2作为“order”进行匹配。 - juharr
根据查询的大小和项目是否由字符串标识,可以轻松添加.Distinct()。 - Michael Demars
“Distinct” 不起作用,因为重复项不是完全相同的。基本上一个是 <M1,M2,B2>,另一个是 <M2,M1,B2>。 - juharr

1
您可以使用查找表(警告:未经测试的代码):

var lookup = orders
    .SelectMany(ord => ord.Components.Select(cmp => new { Order = ord, Component = cmp)
    .ToLookup(obj => obj.Component /* or obj.Component.Name, if you prefer */)
    .Where(lkp => lkp.Count() > 1);

foreach(var orders in lookup)
{
    // orders.Key is the component, orders is an Enumeration of orders containing that component.
}

0
如果您在 Component 和 Order 上实现 IEquatable,并且覆盖 object.Equals()(作为良好的实践),则可以执行以下操作:
var ordersA = new List<Order> { order1, order2, order3 };
var ordersB = new List<Order> { order2, order3 };

bool equal = ordersA.Equals(ordersB);

Component 上的“equals”实现还可以通过使用集合交集来获取共有组件,例如:

var compA = new List<Component> { c1, c2, c3};
var compB = new List<Component> { c2, c3};

var commonComponents = compA.Intersect(compB);

你甚至可以在 Order 列表上使用 Intersect

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