using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
static void Main(string[] args)
{
var list1 = new int?[] {1, 2, 3, 4, 5};
var list2 = new int?[] {3, 4, 5, 6, 7};
var joined = list1.FullOuterJoin(list2, a => a, b => b, (a, b, c) => new {list1=a, list2=b});
joined.ToList().ForEach(Console.WriteLine);
}
}
internal static class MyExtensions
{
private static IDictionary<TK, IEnumerable<TV>> ToDictionary<TK,TV>(this IEnumerable<IGrouping<TK, TV>> grouping)
{
return grouping.ToDictionary(g => g.Key, g => g.AsEnumerable());
}
private static IEnumerable<TV> OuterGet<TK, TV>(this IDictionary<TK, IEnumerable<TV>> dict, TK k, TV d=default(TV))
{
IEnumerable<TV> result;
return dict.TryGetValue(k, out result) ? result : new [] { d };
}
internal static IList<TR> FullOuterJoin<TA, TB, TK, TR>(this IEnumerable<TA> a,
IEnumerable<TB> b,
Func<TA, TK> selectKeyA, Func<TB, TK> selectKeyB,
Func<TA, TB, TK, TR> projection,
TA defaultA=default(TA), TB defaultB=default(TB))
{
var adict = a.GroupBy(selectKeyA).ToDictionary();
var bdict = b.GroupBy(selectKeyB).ToDictionary();
var keys = adict.Keys.Union(bdict.Keys);
var join = from key in keys
from xa in adict.OuterGet(key, defaultA)
from xb in bdict.OuterGet(key, defaultB)
select projection(xa, xb, key);
return join.ToList();
}
}
dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLkxpbnE7CgpzdGF0aWMgY2xhc3MgUHJvZ3JhbQp7CiAgICBzdGF0aWMgdm9pZCBNYWluKHN0cmluZ1tdIGFyZ3MpCiAgICB7CiAgICAgICAgdmFyIGxpc3QxID0gbmV3IGludD9bXSB7MSwgMiwgMywgNCwgNX07CiAgICAgICAgdmFyIGxpc3QyID0gbmV3IGludD9bXSB7MywgNCwgNSwgNiwgN307CgogICAgICAgIHZhciBqb2luZWQgPSBsaXN0MS5GdWxsT3V0ZXJKb2luKGxpc3QyLCBhID0+IGEsIGIgPT4gYiwgKGEsIGIsIGMpID0+IG5ldyB7bGlzdDE9YSwgbGlzdDI9Yn0pOwoKICAgICAgICBqb2luZWQuVG9MaXN0KCkuRm9yRWFjaChDb25zb2xlLldyaXRlTGluZSk7CiAgICB9Cn0KCmludGVybmFsIHN0YXRpYyBjbGFzcyBNeUV4dGVuc2lvbnMKewogICAgcHJpdmF0ZSBzdGF0aWMgSURpY3Rpb25hcnk8VEssIElFbnVtZXJhYmxlPFRWPj4gVG9EaWN0aW9uYXJ5PFRLLFRWPih0aGlzIElFbnVtZXJhYmxlPElHcm91cGluZzxUSywgVFY+PiBncm91cGluZykKICAgIHsKICAgICAgICByZXR1cm4gZ3JvdXBpbmcuVG9EaWN0aW9uYXJ5KGcgPT4gZy5LZXksIGcgPT4gZy5Bc0VudW1lcmFibGUoKSk7CiAgICB9CgogICAgcHJpdmF0ZSBzdGF0aWMgSUVudW1lcmFibGU8VFY+IE91dGVyR2V0PFRLLCBUVj4odGhpcyBJRGljdGlvbmFyeTxUSywgSUVudW1lcmFibGU8VFY+PiBkaWN0LCBUSyBrLCBUViBkPWRlZmF1bHQoVFYpKQogICAgewogICAgICAgIElFbnVtZXJhYmxlPFRWPiByZXN1bHQ7CiAgICAgICAgcmV0dXJuIGRpY3QuVHJ5R2V0VmFsdWUoaywgb3V0IHJlc3VsdCkgPyByZXN1bHQgOiBuZXcgW10geyBkIH07CiAgICB9CgogICAgaW50ZXJuYWwgc3RhdGljIElMaXN0PFRSPiBGdWxsT3V0ZXJKb2luPFRBLCBUQiwgVEssIFRSPih0aGlzIElFbnVtZXJhYmxlPFRBPiBhLCAKICAgICAgICAgICAgSUVudW1lcmFibGU8VEI+IGIsIAogICAgICAgICAgICBGdW5jPFRBLCBUSz4gc2VsZWN0S2V5QSwgRnVuYzxUQiwgVEs+IHNlbGVjdEtleUIsIAogICAgICAgICAgICBGdW5jPFRBLCBUQiwgVEssIFRSPiBwcm9qZWN0aW9uLCAKICAgICAgICAgICAgVEEgZGVmYXVsdEE9ZGVmYXVsdChUQSksIFRCIGRlZmF1bHRCPWRlZmF1bHQoVEIpKQogICAgewogICAgICAgIHZhciBhZGljdCA9IGEuR3JvdXBCeShzZWxlY3RLZXlBKS5Ub0RpY3Rpb25hcnkoKTsKICAgICAgICB2YXIgYmRpY3QgPSBiLkdyb3VwQnkoc2VsZWN0S2V5QikuVG9EaWN0aW9uYXJ5KCk7CgogICAgICAgIHZhciBrZXlzID0gYWRpY3QuS2V5cy5VbmlvbihiZGljdC5LZXlzKTsKCiAgICAgICAgdmFyIGpvaW4gPSBmcm9tIGtleSBpbiBrZXlzCiAgICAgICAgICAgIGZyb20geGEgaW4gYWRpY3QuT3V0ZXJHZXQoa2V5LCBkZWZhdWx0QSkKICAgICAgICAgICAgZnJvbSB4YiBpbiBiZGljdC5PdXRlckdldChrZXksIGRlZmF1bHRCKQogICAgICAgICAgICBzZWxlY3QgcHJvamVjdGlvbih4YSwgeGIsIGtleSk7CgogICAgICAgIHJldHVybiBqb2luLlRvTGlzdCgpOwogICAgfQp9Cgo=