Linq Query To Combine 2 Lists

社会主义新天地 提交于 2021-02-08 06:37:33

问题


I have 2 lists and I need to combine the joining values from A and B, but also include the values from A and B that don't match the join.

class TypeA
{
    public string Key { get; set; }
    public int ValueA { get; set; }
}

class TypeB
{
    public string Key { get; set; }
    public int ValueB { get; set; }
}

class TypeAB
{
    public string Key { get; set; }
    public int ValueA { get; set; }
    public int ValueB { get; set; }
}

var listA = new List<TypeA>
{
    new TypeA { Key = "one", Value = 1 },
    new TypeA { Key = "two", Value = 2 },
};

var listB = new List<TypeB>
{
    new TypeB { Key = "two", Value = 2 },
    new TypeB { Key = "three", Value = 3 },
};

I want these lists combined to equal this:

var listAB = new List<TypeAB>
{
    new TypeAB { Key = "one", ValueA = 1, ValueB = null },
    new TypeAB { Key = "two", ValueA = 2, ValueB = 2 },
    new TypeAB { Key = "three", ValueA = null, ValueB = 3 },
};

What is a Linq statement that will do this? I've been playing around and can't quite get there. I can get almost there by doing a left outer join on A to B and Union that to a left outer join on B to A, but I get duplicate Intersection values.

Update

Here is what I did based on George's answer:

var joined =
    ( from a in listA
      join b in listB
        on a.Key equals b.Key
        into listBJoin
      from b in listBJoin.DefaultIfEmpty( new TypeB() )
      select new TypeAB
      {
        Key = a.Key,
        ValueA = a.ValueA,
        ValueB = b.ValueB,
      } ).Union(
        from b in listB
        where !listA.Any( d => d.Key == b.Key )
        select new TypeAB
        {
            Key = b.Key,
            ValueB = b.ValueB,
        }
        ).ToList();

回答1:


Edit: Fixed up. This is sloppy but it should work.

    listA  //Start with lisA
.Where(a=>!listB.Any(b=>a.Key == b.Key))  // Remove any that are duplicated in listB
.Select(a => new TypeAB() { Key=a.Key, ValueA=a.Value})  // Map each A to an AB
.Union(
  listB.Select(b => {
      var correspondingA = listA.FirstOrDefault(a => a.Key == b.Key);  //Is there an a that corresponds?
    return new TypeAB() { Key=b.Key, 
      ValueB=b.Value,  //Value B is easy
      ValueA= correspondingA!=null ? (int?)correspondingA.Value : null  //If there is an A than map its value
    };
  })
)

As an aside, if you're using this as some sort of domain operation, TypeA and TypeB should probably be based on some sort AorBIsAConceptThatHasMeaningInTheDomain base class. That's just a general rule whenever you find yourself combining lists. If no such concept exists, then you probably don't need to be combining the lists.

On the other hand, if you're doing this as part of a mapping - such as mapping domain objects to UI - you might be able to simplify your code somewhat by using anonymous types instead of a TypeAB class. (Or maybe not, this one is up to personal preference)

Edit Edit Here's a slightly more intellectually interesting answer using hashes

        var listAB = listA.Cast<object>().Union(listB.Cast<object>()).ToLookup(x => x is TypeA ? (x as TypeA).Key : (x as TypeB).Key)
                .Select(kv => {
                    var a = kv.FirstOrDefault(x => x is TypeA) as TypeA;
                    var b = kv.FirstOrDefault(x => x is TypeB) as TypeB;
                    return new TypeAB() {
                        Key = kv.Key,
                        ValueA = a != null ? (int?)a.Value : null,
                        ValueB = b != null ? (int?)b.Value : null
                    };
                }).ToList();



回答2:


For exactly this scenario, in our project we use an extension method called Merge.

public static class Extensions
{
    public static IEnumerable<TResult> Merge<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftList, 
        IEnumerable<TRight> rightList, 
        Func<TLeft, TKey> leftKeySelector, 
        Func<TRight, TKey> rightKeySelector, 
        Func<TKey, IEnumerable<TLeft>, IEnumerable<TRight>, TResult> combiner)
    {
        var leftLookup = leftList.ToLookup(leftKeySelector);
        var rightLookup = rightList.ToLookup(rightKeySelector);

        var keys = leftLookup.Select(g => g.Key).Concat(rightLookup.Select(g => g.Key)).Distinct();
        return keys.Select(key => combiner(key, leftLookup[key], rightLookup[key]));        
    }
}

You can use Merge like this

var listAB = listA.Merge(
    listB,
    a => a.Key,
    b => b.Key,
    (key, aItems, bItems) => new TypeAB 
    { 
        Key = key, 
        ValueA = aItems.Select(a => (int?)a.Value).SingleOrDefault(), 
        ValueB = bItems.Select(b => (int?)b.Value).SingleOrDefault()
    });


来源:https://stackoverflow.com/questions/1930288/linq-query-to-combine-2-lists

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!