LINQ - Full Outer Join

后端 未结 16 1588
既然无缘
既然无缘 2020-11-21 22:45

I have a list of people\'s ID and their first name, and a list of people\'s ID and their surname. Some people don\'t have a first name and some don\'t have a surname; I\'d l

16条回答
  •  我寻月下人不归
    2020-11-21 23:17

    I like sehe's answer, but it does not use deferred execution (the input sequences are eagerly enumerated by the calls to ToLookup). So after looking at the .NET sources for LINQ-to-objects, I came up with this:

    public static class LinqExtensions
    {
        public static IEnumerable FullOuterJoin(
            this IEnumerable left,
            IEnumerable right,
            Func leftKeySelector,
            Func rightKeySelector,
            Func resultSelector,
            IEqualityComparer comparator = null,
            TLeft defaultLeft = default(TLeft),
            TRight defaultRight = default(TRight))
        {
            if (left == null) throw new ArgumentNullException("left");
            if (right == null) throw new ArgumentNullException("right");
            if (leftKeySelector == null) throw new ArgumentNullException("leftKeySelector");
            if (rightKeySelector == null) throw new ArgumentNullException("rightKeySelector");
            if (resultSelector == null) throw new ArgumentNullException("resultSelector");
    
            comparator = comparator ?? EqualityComparer.Default;
            return FullOuterJoinIterator(left, right, leftKeySelector, rightKeySelector, resultSelector, comparator, defaultLeft, defaultRight);
        }
    
        internal static IEnumerable FullOuterJoinIterator(
            this IEnumerable left,
            IEnumerable right,
            Func leftKeySelector,
            Func rightKeySelector,
            Func resultSelector,
            IEqualityComparer comparator,
            TLeft defaultLeft,
            TRight defaultRight)
        {
            var leftLookup = left.ToLookup(leftKeySelector, comparator);
            var rightLookup = right.ToLookup(rightKeySelector, comparator);
            var keys = leftLookup.Select(g => g.Key).Union(rightLookup.Select(g => g.Key), comparator);
    
            foreach (var key in keys)
                foreach (var leftValue in leftLookup[key].DefaultIfEmpty(defaultLeft))
                    foreach (var rightValue in rightLookup[key].DefaultIfEmpty(defaultRight))
                        yield return resultSelector(leftValue, rightValue, key);
        }
    }
    

    This implementation has the following important properties:

    • Deferred execution, input sequences will not be enumerated before the output sequence is enumerated.
    • Only enumerates the input sequences once each.
    • Preserves order of input sequences, in the sense that it will yield tuples in the order of the left sequence and then the right (for the keys not present in left sequence).

    These properties are important, because they are what someone new to FullOuterJoin but experienced with LINQ will expect.

提交回复
热议问题