开发者

LINQ merging two lists(full outer join on composite keys)

开发者 https://www.devze.com 2023-01-24 01:59 出处:网络
I have two lists IEnumerable<Citrus> grapefruit = citrusList.Where(x => x.IsSmall == false);

I have two lists

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

I want to put my all of Citrus in a PackingContainer, but I want to first make tangelos-- a combination of grapefruit and tangerine-- from my grapefruits and tangerines where the Citrus.Color = orange, Citrus.flavor = ver开发者_开发问答y tangy, Citrus.Texture = grainy and the Citrus.State = ripe

Right now I have nested foreach loops that check

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

But I'm sure there's a better way to do this. I want to essentially do a full outer join (union all grapefruit and tangerines, but make tangelos out of the intersection). My end goal is to have a PackingContainer that has some grapefruit, some tangerines, and some tangelos in it. I'm sure there's a more elegant way to do that in LINQ.

...but I can't figure it out from http://msdn.microsoft.com/en-us/library/bb907099.aspx and http://msdn.microsoft.com/en-us/library/bb384063.aspx and it's not exactly a Union because I'm modifying intersecting members (http://msdn.microsoft.com/en-us/library/bb341731.aspx)

Little help?


You don't want a full outer join for that, or you'd wind up making some tangelos without Grapefruit and some tangelos without Tangerines.

here's an inner join.

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

Even that is suspect. What if 3 Grapefruit match 1 Tangerine, then you get 3 Tangelos!

Try this filtering to get only one Tangelo per tangerine:

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

Of course, once you have a List of Tangelos, you can pack them by

container.AddRange(tangelos);


It actually sounds like you need an inner join, not an outer. Your nested for loops are actually performing the equivalent of an inner join. At any rate:

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

Where 'Map' is a 'ForEach'-esque extension method for IEnumerables:

public static void Map<T>(this IEnumerable<T> source, Action<T> func)
{
    foreach (T i in source)
        func(i);
}

EDIT: Fair enough. From the question it sounded like you were only interested in the tangelos. Here is an Outer Join version (This is untested, so let me know if anything doesn't work!):

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

And then you can add them to the container using the map method or the AddRange method from David B's answer.


I think this does the trick:

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);
0

精彩评论

暂无评论...
验证码 换一张
取 消