LINQ中的关系划分?

Posted

技术标签:

【中文标题】LINQ中的关系划分?【英文标题】:Relational division in LINQ? 【发布时间】:2013-04-25 08:15:29 【问题描述】:

关系部门是Codd 的原始关系运算符之一,俗称提供所有零件的供应商。已经有各种 SQL 翻译,例如Celko 以the pilots who can fly all the planes in the hangar 为例讨论了几种方法。

我更喜欢“与集合操作员的划分”,因为它是“有剩余的”(即威尔逊也可以驾驶 F-17 战斗机,但机库中没有)以及它如何处理当divisor 是空集(即当机库为空时,所有飞行员都返回):

WITH PilotSkills
     AS
     (
      SELECT * 
        FROM (
              VALUES ( 'Celko', 'Piper Cub' ), 
                     ( 'Higgins', 'B-52 Bomber' ), ( 'Higgins', 'F-14 Fighter' ),
                     ( 'Higgins', 'Piper Cub' ), 
                     ( 'Jones', 'B-52 Bomber' ), ( 'Jones', 'F-14 Fighter' ),
                     ( 'Smith', 'B-1 Bomber' ), ( 'Smith', 'B-52 Bomber' ),
                     ( 'Smith', 'F-14 Fighter' ),
                     ( 'Wilson', 'B-1 Bomber' ), ( 'Wilson', 'B-52 Bomber' ),
                     ( 'Wilson', 'F-14 Fighter' ), ( 'Wilson', 'F-17 Fighter' )
             ) AS T ( pilot_name, plane_name )
     ), 
     Hangar
     AS
     (
      SELECT * 
        FROM (
              VALUES ( 'B-1 Bomber' ), 
                     ( 'B-52 Bomber' ), 
                     ( 'F-14 Fighter' )
             ) AS T ( plane_name )
     )
SELECT DISTINCT pilot_name 
  FROM PilotSkills AS P1
 WHERE NOT EXISTS (
                   SELECT plane_name 
                     FROM Hangar
                   EXCEPT
                   SELECT plane_name
                     FROM PilotSkills AS P2
                    WHERE P1.pilot_name = P2.pilot_name
                  );

现在我需要在 LINQ to Objects 中执行此操作。这是一个建议的直接翻译:

var hangar = new [] 
 
    new  PlaneName = "B-1 Bomber" ,
    new  PlaneName = "F-14 Fighter" ,
    new  PlaneName = "B-52 Bomber" 
.AsEnumerable();

var pilotSkills = new [] 
 
    new  PilotName = "Celko", PlaneName = "Piper Cub" ,
    new  PilotName = "Higgins", PlaneName = "B-52 Bomber" ,
    new  PilotName = "Higgins", PlaneName = "F-14 Fighter" ,
    new  PilotName = "Higgins", PlaneName = "Piper Cub" ,
    new  PilotName = "Jones", PlaneName = "B-52 Bomber" ,
    new  PilotName = "Jones", PlaneName = "F-14 Fighter" ,
    new  PilotName = "Smith", PlaneName = "B-1 Bomber" ,
    new  PilotName = "Smith", PlaneName = "B-52 Bomber" ,
    new  PilotName = "Smith", PlaneName = "F-14 Fighter" ,
    new  PilotName = "Wilson", PlaneName = "B-1 Bomber" ,
    new  PilotName = "Wilson", PlaneName = "B-52 Bomber" ,
    new  PilotName = "Wilson", PlaneName = "F-14 Fighter" ,
    new  PilotName = "Wilson", PlaneName = "F-17 Fighter" 
.AsEnumerable();

var actual = pilotSkills.Where
(
    p1 => hangar.Except
    ( 
        pilotSkills.Where( p2 => p2.PilotName == p1.PilotName )
                    .Select( p2 => new  p2.PlaneName  )
    ).Any() == false 
).Select( p1 => new  p1.PilotName  ).Distinct();

var expected = new [] 

    new  PilotName = "Smith" ,
    new  PilotName = "Wilson" 
;

Assert.That( actual, Is.EquivalentTo( expected ) );

作为LINQ is supposedly based on the relational algebra,直接翻译似乎是合理的。但是有更好的“原生”LINQ 方法吗?


考虑到@Daniel Hilgarth 的回答,在 .NET Land 中,数据很可能首先被“分组”:

var pilotSkills = new [] 
 
    new  PilotName = "Celko", 
            Planes = new [] 
             new  PlaneName = "Piper Cub" ,  ,
    new  PilotName = "Higgins", 
            Planes = new [] 
             new  PlaneName = "B-52 Bomber" , 
              new  PlaneName = "F-14 Fighter" , 
              new  PlaneName = "Piper Cub" ,  ,
    new  PilotName = "Jones", 
            Planes = new [] 
             new  PlaneName = "B-52 Bomber" , 
              new  PlaneName = "F-14 Fighter" ,  ,
    new  PilotName = "Smith", 
            Planes = new [] 
             new  PlaneName = "B-1 Bomber" , 
              new  PlaneName = "B-52 Bomber" , 
              new  PlaneName = "F-14 Fighter" ,  ,
    new  PilotName = "Wilson", 
            Planes = new [] 
             new  PlaneName = "B-1 Bomber" , 
              new  PlaneName = "B-52 Bomber" , 
              new  PlaneName = "F-14 Fighter" , 
              new  PlaneName = "F-17 Fighter" ,  ,
;

...仅投影名称是任意的,使潜在的解决方案更加直接:

 // Easy to understand at a glance:
var actual1 = pilotSkills.Where( x => hangar.All( y => x.Planes.Contains(y) ));

// Potentially more efficient:
var actual = pilotSkills.Where( x => !hangar.Except( x.Planes ).Any() );

【问题讨论】:

你不需要AsEnumerable(),数组是IEnumerable Codd 的除法不是原始的。并且非正式地返回“供应所有零件的供应商”至少供应一个零件。 (虽然我同意你的非正式措辞很常见。)所以它实际上并没有那么有用。 “方法”不是原始的“翻译”,它们是原始 和不同但让人想起 运算符的翻译。 (根据关系子集运算符最好/最容易推理,然后转换为代数/微积分。) PS“LINQ 据说基于关系代数”-哈哈。灵感来自,好的。 @philipxy 引用我引用的文章:“本文将 monads 和 LINQ 描述为关系代数的概括”。如果您有反驳的引用,我会非常感兴趣。同样,我并不是要暗示除法是原始的,我认为我没有;我链接到的文章(有点非正式地)指的是托德和罗姆利的文章。但是,如果您有一些关于除法不是原始运算符的内容供我阅读,那么我们将不胜感激。 【参考方案1】:

以下应该产生相同的结果:

pilotSkills.GroupBy(x => x.PilotName, x => x.PlaneName)
           .Where(g => hangar.All(y => g.Contains(y.PlaneName)))

这将为每位飞行员返回一个可以驾驶机库中所有飞机的小组。 组的关键是飞行员的名字,组的内容是飞行员能飞的所有飞机,包括那些不在机库的飞机。

如果您现在只想要飞行员,您可以在查询末尾添加.Select(g => new PilotName = g.Key )


将上述方法与Except一起使用,使其更接近OP的原版:

pilotSkills.GroupBy(x => x.PilotName, x => new  x.PlaneName  )
           .Where(g => !hangar.Except(g).Any());

第二个查询可能更好,因为它只迭代g 一次;第一个带有Contains 的查询会迭代它 N 次,其中 N 是机库中的飞机数量。

【讨论】:

*$(*%£" 只需击败我即可获得答案,但是您确实需要最后选择才能获得与 OP 完全相同的结果... @BobVale:是的,我比你快了 10 分钟 :p 是的,我已经添加了选择。对我来说,更重要的是证明所有飞机都已归还,而不仅仅是机库中的那些。 @BobVale:我讨厌这种事情发生在我身上...... :) @BobVale:顺便说一句:您的答案不正确。你把它换了。你归还了那些他们可以驾驶的每架飞机都存在于机库中的人。例如。你返回了Jones。他无法驾驶机库中的所有飞机。但他能飞的所有飞机都在那里。 我推了这个,这就是为什么我删除了我的,因为你给出了几乎相同的答案,只是条件正确【参考方案2】:

使用@Daniel Hilgarth 的回答中的方法,但更接近我的原作:

var actual = pilotSkills
                 .GroupBy(x => x.PilotName, x => new  x.PlaneName )
                 .Where(g => !hangar.Except(g).Any())
                 .Select(x => new  PilotName = x.Key );

【讨论】:

您使用Except 的解决方案可能会更好,因为它只迭代g 一次。我使用Contains 的解决方案将其迭代 N 次,其中 N 是机库中的飞机数量。

以上是关于LINQ中的关系划分?的主要内容,如果未能解决你的问题,请参考以下文章

Spark Stage的划分

如何解决这个关系划分查询?

ip网段划分

如何找到与其他记录组匹配的记录组(关系划分?)

光纤交换机划分ZONE 的作用、意义是啥?

文本挖掘:社交网络社群划分