C#Linq对重复值的完全外连接

时间:2017-12-16 09:06:25

标签: c# linq outer-join

我有两个具有此类型的IQueryable集合

public class Property  
{  
   public string Name {get; set;}  
}

集合1,具有以下名称值:

A  
A  
A  
B  

集合2,具有以下名称值:

A  
B  
B

我想得到的是第三个集合,其中集合1和2的名称值匹配,如果没有匹配,则为null (empty),如下所示:

Result Collection:  

A     A
A     null  
A     null  
B     B  
null  B

如何用C#,LINQ实现这一点?

5 个答案:

答案 0 :(得分:4)

using System;
using System.Collections.Generic;
using System.Linq;    

namespace Testing
{
    public class Property
    {
        public string Name { get; set; }

        public override bool Equals(object obj)
        {
            var item = obj as Property;

            if (item == null)
            {
                return false;
            }
            return item.Name == Name;
        }

        public override int GetHashCode()
        {
            return Name.GetHashCode();
        }
    }

    public class JoinedProperty
    {
        public Property Name1 { get; set; }
        public Property Name2 { get; set; }

        public override string ToString()
        {
            return (Name1 == null ? "" : Name1.Name)
                + (Name2 == null ? "" : Name2.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var list1 = new List<Property>
            {
                new Property{ Name = "A" },
                new Property{ Name = "A" },
                new Property{ Name = "A" },
                new Property{ Name = "B" }
            };

            var list2 = new List<Property>
            {
                new Property{ Name = "A" },
                new Property{ Name = "B" },
                new Property{ Name = "B" }
            };

            var allLetters = list1.Union(list2).Distinct().ToList();

            var result = new List<JoinedProperty>();

            foreach (var letter in allLetters)
            {
                var list1Count = list1.Count(l => l.Name == letter.Name);
                var list2Count = list2.Count(l => l.Name == letter.Name);

                var matchCount = Math.Min(list1Count, list2Count);

                addValuesToResult(result, letter, letter, matchCount);

                var difference = list1Count - list2Count;

                if(difference > 0)
                {
                    addValuesToResult(result, letter, null, difference);                   
                }
                else
                {
                    difference = difference * -1;
                    addValuesToResult(result,null, letter, difference);                   
                }
            }
            foreach(var res in result)
            {
                Console.WriteLine(res.ToString());
            }
            Console.ReadLine();                
        }

        private static void addValuesToResult(List<JoinedProperty> result, Property letter1, Property letter2, int count)
        {
            for (int i = 0; i < count; i++)
            {
                result.Add(new JoinedProperty
                {
                    Name1 = letter1,
                    Name2 = letter2
                });
            }
        }
    }
}

运行此命令即可获得结果

AA
A
A
BB
B

并且结果列表的内容就是你所追求的。

编辑:更新了我的答案,以使用指定的属性。

答案 1 :(得分:1)

对于这个问题似乎很感兴趣所以我试图提出一个更通用的解决方案。我从这个链接中获取了灵感https://www.codeproject.com/Articles/488643/LinQ-Extended-Joins

我已经创建了一个fullouterjoin扩展方法,它可以执行操作所要求的操作。不确定fullouterjoin是否是正确的名称。

我使用了我的扩展方法来解决操作问题。

using System;
using System.Collections.Generic;
using System.Linq;


namespace Testing
{


    public class Property
    {
        public string Name { get; set; }
    }

    public class JoinedProperty
    {
        public Property Name1 { get; set; }
        public Property Name2 { get; set; }

        public override string ToString()
        {
            return (Name1 == null ? "" : Name1.Name)
                + (Name2 == null ? "" : Name2.Name);
        }  
    }

    class Program
    {
        static void Main(string[] args)
        {
            var list1 = new List<Property>
        {
            new Property{ Name = "A" },
            new Property{ Name = "A" },
            new Property{ Name = "A" },
            new Property{ Name = "B" }
        };

            var list2 = new List<Property>
        {
            new Property{ Name = "A" },
            new Property{ Name = "B" },
            new Property{ Name = "B" }
        };



            var result = list1.FullOuterJoin(
                list2,
                p1 => p1.Name,
                p2 => p2.Name,
                (p1, p2) => new JoinedProperty
                {
                    Name1 = p1,
                    Name2 = p2
                }).ToList();


            foreach (var res in result)
            {
                Console.WriteLine(res.ToString());
            }
            Console.ReadLine();

        }

    }

    public static class MyExtensions
    {



        public static IEnumerable<TResult>
            FullOuterJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source,
                                IEnumerable<TInner> inner,
                                Func<TSource, TKey> pk,
                                Func<TInner, TKey> fk,
                                Func<TSource, TInner, TResult> result)
            where TSource : class where TInner : class
        {

            var fullList = source.Select(s => new Tuple<TSource, TInner>(s, null))
                .Concat(inner.Select(i => new Tuple<TSource, TInner>(null, i)));


            var joinedList = new List<Tuple<TSource, TInner>>();

            foreach (var item in fullList)
            {
                var matchingItem = joinedList.FirstOrDefault
                    (
                        i => matches(i, item, pk, fk)
                    );

                if(matchingItem != null)
                {
                    joinedList.Remove(matchingItem);
                    joinedList.Add(combinedMatchingItems(item, matchingItem));
                }
                else
                {
                    joinedList.Add(item);
                }
            }
            return joinedList.Select(jl => result(jl.Item1, jl.Item2)).ToList();

        }

        private static Tuple<TSource, TInner> combinedMatchingItems<TSource, TInner>(Tuple<TSource, TInner> item1, Tuple<TSource, TInner> item2)
            where TSource : class
            where TInner : class
        {
            if(item1.Item1 == null && item2.Item2 == null && item1.Item2 != null && item2.Item1 !=null)
            {
                return new Tuple<TSource, TInner>(item2.Item1, item1.Item2);
            }

            if(item1.Item2 == null && item2.Item1 == null && item1.Item1 != null && item2.Item2 != null)
            {
                return new Tuple<TSource, TInner>(item1.Item1, item2.Item2);
            }

            throw new InvalidOperationException("2 items cannot be combined");
        }

        public static bool matches<TSource, TInner, TKey>(Tuple<TSource, TInner> item1, Tuple<TSource, TInner> item2, Func<TSource, TKey> pk, Func<TInner, TKey> fk)
            where TSource : class
            where TInner : class
        {          

            if (item1.Item1 != null && item1.Item2 == null && item2.Item2 != null && item2.Item1 == null && pk(item1.Item1).Equals(fk(item2.Item2)))
            {
                return true;
            }

            if (item1.Item2 != null && item1.Item1 == null && item2.Item1 != null && item2.Item2 == null && fk(item1.Item2).Equals(pk(item2.Item1)))
            {
                return true;
            }

            return false;

        }

    }
}

答案 2 :(得分:0)

我认为,最好的选择就是使用loop;

        var listA = new List<Property>
        {
            new Property{ Name = "A" },
            new Property{ Name = "A" },
            new Property{ Name = "A" },
            new Property{ Name = "B" }
        };
        var listB = new List<Property>
        {
            new Property{ Name = "A" },
            new Property{ Name = "B" },
            new Property{ Name = "B" }
        };
        var joinedList = new List<JoinedProperty>();
        for (int i = 0; i < listA.Count; i++)
        {
            var property = new JoinedProperty
            {
                AName = listA[i].Name,
                BName = null
            };
            if (listB.Count < i + 1)
            {
                continue;
            }
            if (listA[i].Name == listB[i].Name)
            {
                property.BName = listA[i].Name;
            }
            joinedList.Add(property);
        }
        for (int i = 0; i < listB.Count; i++)
        {
            var property = new JoinedProperty
            {
                AName = null,
                BName = listB[i].Name
            };
            if (listA.Count < i + 1)
            {
                continue;
            }
            if (listB[i].Name == listA[i].Name)
            {
                property.AName = listB[i].Name;
            }
            joinedList.Add(property);
        }

        public class JoinedProperty
        {
             public string AName { get; set; }
             public string BName { get; set; }
        }

另外,我认为,您的输出示例缺少一个元素;

null B

<强>输出

A     A
A     null  
A     null  
B     B  
null  B
null  B

答案 3 :(得分:0)

public class Property
{
    public string Name { get; set; }
}

var list1 = new List<Property>
{
    new Property { Name ="A" },
    new Property { Name ="A" },
    new Property { Name ="A" },
    new Property { Name ="B" }
};

var list2 = new List<Property>
{
    new Property { Name ="A" },
    new Property { Name ="B" },
    new Property { Name ="B" }
};

var r = new List<string>();
int x1 = 0, x2 = 0;
int count1 = list1.Count, count2 = list2.Count;

while (true)
{
    if (x1 == count1 && x2 == count2) break;

    if (x1 < count1 && x2 == count2)
    {
        r.Add($"{list1[x1].Name}\tNULL");
        ++x1;
    }
    else if (x1 == count1 && x2 < count2)
    {
        r.Add($"NULL\t{list2[x2].Name}");
        ++x2;
    }
    else
    {
        if (list1[x1].Name == list2[x2].Name)
        {
            r.Add($"{list1[x1].Name}\t{list2[x2].Name}");
            ++x1; ++x2;
        }
        else
        {
            r.Add($"{list1[x1].Name}\tNULL");
            ++x1;
        }
    }
}

<强>解释

这个想法是管理名单中的职位 - 即我们是否应该提升职位。一旦查找了所有位置,循环就会退出。

答案 4 :(得分:0)

你问过LINQ函数,没有,但你可以扩展它,所以它可以用于你想要这个技巧的任何两个序列。

您所要做的就是编写IEnumerable的扩展函数,类似于所有其他LINQ函数。

请参阅Extension Methods Demystified

public static class MyEnumerableExtensions
{
    public IEnumerable<System.Tuple<T, T>> EqualityZip<T>(this IEnumerable<T> sourceA,
        IEnumerable<T> sourceB)
    {
        // TODO: check for parameters null

        var enumeratorA = sourceA.GetEnumerator();
        var enumeratorB = sourceB.GetEnumerator();

        // enumerate as long as we have elements in A and in B:
        bool aAvailable = enumeratorA.MoveNext();
        bool bAvailable = enumeratorB.MoveNext();
        while (aAvailable && bAvailable)
        {   // we have an A element and a B element
            T a = enumeratorA.Current;
            T b = enumeratorB.Current;

            // compare the two elements:
            if (a == b)
            {   // equal: return tuple (a, b)
                yield return Tuple.Create(a, b)
            }
            else
            {   // not equal, return (a, null)
                yield return Tuple.Create(a, (T)null)
            }

            // move to the next element
            aAvailable = enumeratorA.MoveNext();
            bAvailable = enumeratorB.MoveNext();
        }
        // now either we are out of A or out of B

        while (aAvailable)
        {   // we still have A but no B, return (A, null)
            T A = enumeratorA.Current;
            yield return Tuple.Create(A, (T)null);
            aAvailable = enumeratorA.MoveNext();
        }
        while (bAvailable)
        {   // we don't have A, but there are still B, return (null, B)
            T B = enumeratorB.Current;
            yield return Tuple.Create((T)null, B);
            bAvailable = enumeratorB.MoveNext();
        }

        // if there are still A elements without B element: return (a, null)
        while (enumaratorA.Nex
    }
}

用法:

var sequenceA = ...
var sequenceB = ...
var result = sequenceA.EqualityZip(sequenceB);

TODO:使函数更好,可以比较两个不同的类,KeySelectors选择A和B的比较键以及IEqualityCompare:

public static IEnumerable<Tuple<TA, TB> EqualityZip<TA, TB, TKey>(
    this IEnumerable<TA> sourceA,   // the first sequence
    this IEnumerable<TB> sourceB,   // the second sequence
    Func<TA, TKey> keySelectorA,    // the property of sourceA to take
    Func<TB, TKey> keySelectorB,    // the property of sourceB to take
    IEqualityComparer<TKey> comparer)
{
    // TODO: ArgumentNullException if arguments null
    if (comparer==null) comparer = EqualityCompare<TKey>.Default;

     var enumeratorA = sourceA.GetEnumerator();
        var enumeratorB = sourceB.GetEnumerator();

        // enumerate as long as we have elements in A and in B:
        bool aAvailable = enumeratorA.MoveNext();
        bool bAvailable = enumeratorB.MoveNext();
        while (aAvailable && bAvailable)
        {   // we have an A element and a B element
            TKey keyA = keySelectorA(enumeratorA.Current);
            TKey keyB = keySelectorB(enumeratorB.Current);
            if (comparer.Equals(keyA, keyB)
            {
                yield return Tuple.Create(Ta, Tb)
            }
            else

相关问题