更新 1: 真に一般化された拡張メソッドを提供する更新 2: オプションFullOuterJoin
でキー タイプ
のカスタムを受け入れるIEqualityComparer
更新 3 : この実装は最近一部になりましたMoreLinq
- みんなありがとう!
追加された編集FullOuterGroupJoin
( ideone )。私はGetOuter<>
実装を再利用して、これを可能な限りパフォーマンスを低下させましたが、今のところ、最先端の最適化ではなく、「高レベル」のコードを目指しています。
http://ideone.com/O36nWcでライブをご覧ください
static void Main(string[] args)
{
var ax = new[] {
new { id = 1, name = "John" },
new { id = 2, name = "Sue" } };
var bx = new[] {
new { id = 1, surname = "Doe" },
new { id = 3, surname = "Smith" } };
ax.FullOuterJoin(bx, a => a.id, b => b.id, (a, b, id) => new {a, b})
.ToList().ForEach(Console.WriteLine);
}
出力を印刷します。
{ a = { id = 1, name = John }, b = { id = 1, surname = Doe } }
{ a = { id = 2, name = Sue }, b = }
{ a = , b = { id = 3, surname = Smith } }
デフォルトを指定することもできます: http://ideone.com/kG4kqO
ax.FullOuterJoin(
bx, a => a.id, b => b.id,
(a, b, id) => new { a.name, b.surname },
new { id = -1, name = "(no firstname)" },
new { id = -2, surname = "(no surname)" }
)
印刷:
{ name = John, surname = Doe }
{ name = Sue, surname = (no surname) }
{ name = (no firstname), surname = Smith }
使用される用語の説明:
結合は、リレーショナル データベース設計から借用した用語です。
- 結合は、対応するキー
a
を持つ要素が存在する回数だけ要素を繰り返します(つまり、空の場合は何もありません)。データベース用語ではこれを と呼びます。b
b
inner (equi)join
- 外部結合には、対応する要素
a
がに存在しない要素が含まれます。(つまり、空の場合でも結果が得られます)。これは通常、 と呼ばれます。b
b
left join
- 完全外部結合には、対応する要素が他に存在しない場合
a
と同様に、b
からのレコードが含まれます。(つまり、空の場合でも結果が得られます)a
RDBMS で通常見られないのは、グループ結合です[1] :
- グループ結合は、上記と同じことを行いますが
a
、複数の対応するからの要素を繰り返す代わりにb
、対応するキーでレコードをグループ化します。これは、共通キーに基づいて「結合された」レコードを列挙したい場合に便利です。
いくつかの一般的な背景説明も含まれているGroupJoinも参照してください。
[1] (Oracle と MSSQL には、これに対する独自の拡張機能があると思います)
完全なコード
このための一般化された「ドロップイン」拡張クラス
internal static class MyExtensions
{
internal static IEnumerable<TResult> FullOuterGroupJoin<TA, TB, TKey, TResult>(
this IEnumerable<TA> a,
IEnumerable<TB> b,
Func<TA, TKey> selectKeyA,
Func<TB, TKey> selectKeyB,
Func<IEnumerable<TA>, IEnumerable<TB>, TKey, TResult> projection,
IEqualityComparer<TKey> cmp = null)
{
cmp = cmp?? EqualityComparer<TKey>.Default;
var alookup = a.ToLookup(selectKeyA, cmp);
var blookup = b.ToLookup(selectKeyB, cmp);
var keys = new HashSet<TKey>(alookup.Select(p => p.Key), cmp);
keys.UnionWith(blookup.Select(p => p.Key));
var join = from key in keys
let xa = alookup[key]
let xb = blookup[key]
select projection(xa, xb, key);
return join;
}
internal static IEnumerable<TResult> FullOuterJoin<TA, TB, TKey, TResult>(
this IEnumerable<TA> a,
IEnumerable<TB> b,
Func<TA, TKey> selectKeyA,
Func<TB, TKey> selectKeyB,
Func<TA, TB, TKey, TResult> projection,
TA defaultA = default(TA),
TB defaultB = default(TB),
IEqualityComparer<TKey> cmp = null)
{
cmp = cmp?? EqualityComparer<TKey>.Default;
var alookup = a.ToLookup(selectKeyA, cmp);
var blookup = b.ToLookup(selectKeyB, cmp);
var keys = new HashSet<TKey>(alookup.Select(p => p.Key), cmp);
keys.UnionWith(blookup.Select(p => p.Key));
var join = from key in keys
from xa in alookup[key].DefaultIfEmpty(defaultA)
from xb in blookup[key].DefaultIfEmpty(defaultB)
select projection(xa, xb, key);
return join;
}
}