Skip to content

Commit

Permalink
Add FullOuterJoin method
Browse files Browse the repository at this point in the history
  • Loading branch information
fsateler committed Oct 1, 2015
1 parent 379ba62 commit b526df4
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 0 deletions.
102 changes: 102 additions & 0 deletions MoreLinq.Test/FullGroupJoinTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2015 Jonathan Skeet. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;


namespace MoreLinq.Test
{
[TestFixture]
public class FullGroupJoinTest
{
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void FullGroupFirstNull()
{
((IEnumerable<string>)null).FullGroupJoin(Enumerable.Empty<string>(), x => x, x => x, DummySelector);
}

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void FullGroupSecondNull()
{
Enumerable.Empty<string>().FullGroupJoin((IEnumerable<string>)null, x => x, x => x, DummySelector);
}

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void FullGroupFirstKeyNull()
{
Enumerable.Empty<string>().FullGroupJoin(Enumerable.Empty<string>(), (Func<string, string>)null, x => x, DummySelector);
}

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void FullGroupSecondKeyNull()
{
Enumerable.Empty<string>().FullGroupJoin(Enumerable.Empty<string>(), x => x, (Func<string, string>)null, DummySelector);
}

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void FullGroupResultSelectorNull()
{
Enumerable.Empty<string>().FullGroupJoin(Enumerable.Empty<string>(), x => x, x => x, (Func<string, IEnumerable<string>, IEnumerable<string>, string>)null);
}

[Test]
public void FullGroupIsLazy()
{
var listA = new BreakingSequence<int>();
var listB = new BreakingSequence<int>();

listA.FullGroupJoin(listB, x => x, x => x, DummySelector);
Assert.True(true);
}

[Test]
public void FullGroupJoinsResults()
{
var listA = new[] { 1, 2 };
var listB = new[] { 2, 3 };

var result = listA.FullGroupJoin(listB, x => x, x => x, (key, first, second) => new { key, first, second }).ToDictionary(a => a.key);

Assert.AreEqual(3, result.Keys.Count);

Assert.IsEmpty(result[1].second);
Assert.AreEqual(1, result[1].first.Single());

Assert.IsEmpty(result[3].first);
Assert.AreEqual(3, result[3].second.Single());

Assert.IsNotEmpty(result[2].first);
Assert.AreEqual(2, result[2].first.Single());
Assert.IsNotEmpty(result[2].second);
Assert.AreEqual(2, result[2].second.Single());
}

private static T1 DummySelector<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
{
return t1;
}
}
}

1 change: 1 addition & 0 deletions MoreLinq.Test/MoreLinq.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
<Compile Include="TestExtensions.cs" />
<Compile Include="ToDelimitedStringTest.cs" />
<Compile Include="Tuple.cs" />
<Compile Include="FullGroupJoinTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MoreLinq\MoreLinq.csproj">
Expand Down
111 changes: 111 additions & 0 deletions MoreLinq/FullGroupJoin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2008 Jonathan Skeet. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

This comment has been minimized.

Copy link
@atifaziz

atifaziz Oct 28, 2015

System.Text import is unused in FullGroupJoin.cs


// Inspiration & credit: http://stackoverflow.com/a/13503860/6682
static partial class MoreEnumerable
{
/// <summary>
/// Performs a Full Group Join between the <paramref name="first"/> and <paramref name="second"/> sequences.
/// </summary>
/// <remarks>
/// This operator uses deferred execution and streams the results.
/// </remarks>
/// <typeparam name="TFirst">The type of the elements in the first input sequence</typeparam>
/// <typeparam name="TSecond">The type of the elements in the first input sequence</typeparam>
/// <typeparam name="TKey">The type of the key to use to join</typeparam>
/// <typeparam name="TResult">The type of the elements of the resulting sequence</typeparam>
/// <param name="first">First sequence</param>
/// <param name="second">Second secuence</param>
/// <param name="firstKeySelector">The mapping from first sequence to key</param>
/// <param name="secondKeySelector">The mapping from second sequence to key</param>
/// <param name="resultSelector">Function to apply to each pair of elements plus the key</param>
/// <returns>A sequence of elements joined from <paramref name="first"/> and <paramref name="second"/>.
/// </returns>
public static IEnumerable<TResult> FullGroupJoin<TFirst, TSecond, TKey, TResult>(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TKey> firstKeySelector,
Func<TSecond, TKey> secondKeySelector,
Func<TKey, IEnumerable<TFirst>, IEnumerable<TSecond>, TResult> resultSelector)
{
return FullGroupJoin(first, second, firstKeySelector, secondKeySelector, resultSelector, EqualityComparer<TKey>.Default);
}

/// <summary>
/// Performs a Full Group Join between the <paramref name="first"/> and <paramref name="second"/> sequences.
/// </summary>
/// <remarks>
/// This operator uses deferred execution and streams the results.
/// </remarks>
/// <typeparam name="TFirst">The type of the elements in the first input sequence</typeparam>
/// <typeparam name="TSecond">The type of the elements in the first input sequence</typeparam>
/// <typeparam name="TKey">The type of the key to use to join</typeparam>
/// <typeparam name="TResult">The type of the elements of the resulting sequence</typeparam>
/// <param name="first">First sequence</param>
/// <param name="second">Second secuence</param>
/// <param name="firstKeySelector">The mapping from first sequence to key</param>
/// <param name="secondKeySelector">The mapping from second sequence to key</param>
/// <param name="resultSelector">Function to apply to each pair of elements plus the key</param>
/// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <c>TKey</c> is used.</param>
/// <returns>A sequence of elements joined from <paramref name="first"/> and <paramref name="second"/>.
/// </returns>
public static IEnumerable<TResult> FullGroupJoin<TFirst, TSecond, TKey, TResult>(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TKey> firstKeySelector,
Func<TSecond, TKey> secondKeySelector,
Func<TKey, IEnumerable<TFirst>, IEnumerable<TSecond>, TResult> resultSelector,
IEqualityComparer<TKey> comparer)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (firstKeySelector == null) throw new ArgumentNullException("firstKeySelector");
if (secondKeySelector == null) throw new ArgumentNullException("secondKeySelector");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");

return FullGroupJoinImpl(first, second, firstKeySelector, secondKeySelector, resultSelector, comparer);
}

private static IEnumerable<TResult> FullGroupJoinImpl<TFirst, TSecond, TKey, TResult>(IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TKey> firstKeySelector,
Func<TSecond, TKey> secondKeySelector,
Func<TKey, IEnumerable<TFirst>, IEnumerable<TSecond>, TResult> resultSelector,
IEqualityComparer<TKey> comparer)
{
comparer = comparer ?? EqualityComparer<TKey>.Default;

var alookup = first.ToLookup(firstKeySelector, comparer);
var blookup = second.ToLookup(secondKeySelector, comparer);
var keys = alookup.Select(p => p.Key).Union(blookup.Select(p => p.Key), comparer);

var join = keys.Select(key => resultSelector(key, alookup[key], blookup[key]));

// We do the iteration + yield return to delay executing the earlier ToLookup
// until the result is enumerated
foreach (var item in join) {
yield return item;
}
}
}
}
3 changes: 3 additions & 0 deletions MoreLinq/MoreLinq.Portable.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
<Compile Include="ForEach.cs">
<DependentUpon>MoreEnumerable.cs</DependentUpon>
</Compile>
<Compile Include="FullGroupJoin.cs">
<DependentUpon>MoreEnumerable.cs</DependentUpon>
</Compile>
<Compile Include="Generate.cs">
<DependentUpon>MoreEnumerable.cs</DependentUpon>
</Compile>
Expand Down
3 changes: 3 additions & 0 deletions MoreLinq/MoreLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@
<DependentUpon>MoreEnumerable.cs</DependentUpon>
</None>
<Compile Include="SequenceException.cs" />
<Compile Include="FullGroupJoin.cs">
<DependentUpon>MoreEnumerable.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="key.snk" />
Expand Down

0 comments on commit b526df4

Please sign in to comment.