LINQ合并两个列表(复合键上的完全外连接)

时间:2010-11-04 18:26:19

标签: c# linq

我有两个列表

 IEnumerable<Citrus> grapefruit = citrusList.Where(x => x.IsSmall == false);
 IEnumerable<Citrus> tangerines = citrusList.Where(x => x.IsSmall == true);

我想把我所有的柑橘都放在PackingContainer中,但是我想首先制作tangelos--葡萄柚和橘子的组合 - 来自我的葡萄柚和橘子,其中Citrus.Color =橙色,Citrus.flavor =非常浓郁,Citrus.Texture =颗粒状,Citrus.State =成熟

现在我已经嵌套了检查

的foreach循环
 foreach (Citrus fruit in grapefruit)
 {
    foreach (Citrus fruitToo in tangerines)
    {
       PackingContainer container = new PackingContainer();
       if (fruit.Color == fruitToo.Color && 
           fruit.Flavor == fruitToo.Flavor && 
           fruit.Texture == fruitToo.Texture && 
           fruit.State == fruitToo.State)
           { 
              Tangelo tangy = new Tangelo(fruit.Color, fruit.Flavor, fruit.Texture, fruit.State, "A tangelo", new Decimal(0.75);
              container.Add(tangy);
           }
     }
  }

但我确信有更好的方法可以做到这一点。我想基本上做一个完整的外部连接(联合所有的葡萄柚和橘子,但是让tangelos离开交叉点)。我的最终目标是拥有一个包装葡萄柚,一些橘子和一些橘子的PackingContainer。我确信在LINQ中有更优雅的方法。

...但我无法从http://msdn.microsoft.com/en-us/library/bb907099.aspxhttp://msdn.microsoft.com/en-us/library/bb384063.aspx弄清楚它并不是一个联盟,因为我正在修改相交成员(http://msdn.microsoft.com/ en-us / library / bb341731.aspx)

帮助不大?

3 个答案:

答案 0 :(得分:0)

实际上听起来你需要一个内连接,而不是外连接。嵌套的for循环实际上是执行内连接的等价物。无论如何:

grapefruit
 .Join(
  tangerines,
  x => new { Color = x.Color, Flavor = x.Flavor, Texture = x.Texture, State = x.State },
  x => new { Color = x.Color, Flavor = x.Flavor, Texture = x.Texture, State = x.State },
  (o,i) => new Tangelo(o.Color, o.Flavor, o.Texture, o.State, "A tangelo", new Decimal(0.75))
 ).Map(x => container.Add(x));

其中'Map'是IEnumerables的'ForEach'-esque扩展方法:

public static void Map<T>(this IEnumerable<T> source, Action<T> func)
{
    foreach (T i in source)
        func(i);
}
编辑:足够公平。从这个问题来看,听起来你只对tangelos感兴趣。这是一个外部加入版本(这是未经测试的,所以如果有什么不起作用,请告诉我!):

var q =
from fruit in grapefruit.Select(x => new { x.Color, x.Flavor, x.Texture, x.State })
   .Union(tangerines.Select(x => new { x.Color, x.Flavor, x.Texture, x.State }))
join g in grapefruit on fruit equals new { g.Color, g.Flavor, g.Texture, g.State } into jg
from g in jg.DefaultIfEmpty()
join t in tangerines on fruit equals new { t.Color, t.Flavor, t.Texture, t.State } into jt
from t in jt.DefaultIfEmpty()
select  (g == null ? 
   t as Citrus : 
   (t == null ? 
    g as Citrus : 
    new Tangelo(g.Color, g.Flavor, g.Texture, g.State, "A tangelo", new Decimal(0.75)) as Citrus
   )
  );

然后你可以使用map方法或David B的答案中的AddRange方法将它们添加到容器中。

答案 1 :(得分:0)

你不需要一个完整的外部连接,或者你最终会制作一些没有葡萄柚的tangelos和一些没有Tangerines的tangelos。

这是一个内部联接。

List<Tangelo> tangelos = (
from fruit in grapefruit
join fruitToo in tangerines
  on new {fruit.Flavor, fruit.Color, fruit.Flavor, fruit.State}
  equals new {fruitToo.Flavor, fruitToo.Color, fruitToo.Flavor, fruitToo.State}
select new Tangelo(fruit.Color, fruit.Flavor, fruit.Texture, fruit.State,
  "A tangelo", new Decimal(0.75))
).ToList()

即使这是可疑的。如果3 Grapefruit匹配1 Tangerine,那么你得到3 Tangelos!

尝试此过滤只能获得每个橘子一个Tangelo:

List<Tangelo> tangelos = (
from fruit in tangerines
where grapefruit.Any(fruitToo => 
  new {fruit.Flavor, fruit.Color, fruit.Flavor, fruit.State}
  == new {fruitToo.Flavor, fruitToo.Color, fruitToo.Flavor, fruitToo.State})
select new Tangelo(fruit.Color, fruit.Flavor, fruit.Texture, fruit.State,
  "A tangelo", new Decimal(0.75))
).ToList()

当然,一旦你有一个Tangelos列表,你可以通过

打包它们
container.AddRange(tangelos);

答案 2 :(得分:0)

我认为这就是诀窍:

var cs = from c in citrusList
         group c by new { c.Color, c.Flavor, c.Texture, c.State } into gcs
         let gs = gcs.Where(gc => gc.IsSmall == false)
         let ts = gcs.Where(gc => gc.IsSmall == true)
         let Tangelos = gs
            .Zip(ts, (g, t) =>
                new Tangelo(g.Color, g.Flavor, g.Texture, g.State,
                    "A tangelo", new Decimal(0.75)))
         select new
         {
             gcs.Key,
             Grapefruit = gs.Skip(Tangelos.Count()),
             Tangerines = ts.Skip(Tangelos.Count()),
             Tangelos,
         };

var container = new PackingContainer();

container.AddRange(from c in cs
                   from f in c.Grapefruit
                       .Concat(c.Tangerines)
                       .Concat(c.Tangelos.Cast<Citrus>())
                   select f);