まず方法から...
C# のDictionary<TKey,TValue>
同士をマージするコードを書く必要があり、どうせならfor
文ぐるぐる、ContainsKey
で重複チェックして...より LINQ でスマートに書きたいと思い調べたのでまとめます。
var dic1 = new Dictionary<string, int> {
["oarange"] = 100,
["apple"] = 150,
};
var dic2 = new Dictionary<string, int> {
["apple"] = 180,
["banana"] = 200,
};
var merged = dic1.Concat(dic2)
.GroupBy(
pair => pair.Key,
(_, pairs) => pairs.First()
).ToDictionary(
pair => pair.Key,
pair => pair.Value
);
foreach (var (k, v) in merged) {
Console.WriteLine($"key: {k}, value: {v}");
}
key: oarange, value: 100
key: apple, value: 150
key: banana, value: 200
以下は上記のコードの説明です。
興味があれば読んでいただけると嬉しいです。
追記
コメントいただいた以下の方法の方がシンプルです。
var merged = dic1
.Concat(dic2.Where(pair =>
!dic1.ContainsKey(pair.Key))
).ToDictionary(
pair => pair.Key,
pair => pair.Value
);
説明
型を明示すると以下のようになります。
// マージする Dictionary
var dict1 = new Dictionary<string, int> {
["oarange"] = 100,
["apple"] = 150,
};
var dict2 = new Dictionary<string, int> {
["apple"] = 180,
["banana"] = 200,
};
// Dictionary 同士を連結する
IEnumerable<KeyValuePair<string, int>> concated = dict1.Concat(dict2);
// キーが重複する KeyValuePair を削除する
IEnumerable<KeyValuePair<string, int>> pairs = concated.GroupBy(
(KeyValuePair<string, int> pair) => pair.Key,
(string key, IEnumerable<KeyValuePair<string, int>> keyValues) => keyValues.First()
);
// KeyValuePair のシーケンスを Dictionary 化する
Dictionary<string, int> mergedDict = pairs.ToDictionary(
(KeyValuePair<string, int> pair) => pair.Key,
(KeyValuePair<string, int> pair) => pair.Value
);
Dictionary
同士を連結
IEnumerable<KeyValuePair<string, int>> concated = dict1.Concat(dict2);
Concat
でなくUnion
でも連結できます。
ただしUnion
の重複判定はキーでなくGetHashCode
で得られれるハッシュ値とEquals
で行われます。
つまり、和集合にはならずConcat
と同様にToDictionary
の前に重複を弾く必要があります。
Union
の第二引数にIEqualityComparer<Dictionary<string, int>
を実装したクラスを渡せば重複を弾けますが面倒なのでやりません。
キーが重複するKeyValuePair
の削除
IEnumerable<KeyValuePair<string, int>> pairs = concated.GroupBy(
(KeyValuePair<string, int> pair) => pair.Key,
(string key, IEnumerable<KeyValuePair<string, int>> keyValues) => keyValues.First()
);
ToDictionary
はキーが重複すると例外をスローするのでキーが重複するKeyValuePair
を取り除く必要があります。
GroupBy
の第二引数にFunc<TKey,IEnumerable<TElement>,TResult>
を渡すと各グループから結果値を作成することができます。
KeyValuePair
を返しているため結果がIEnumerable<IGrouping<string, int>>
でなくIEnumerable<KeyValuePair<string, int>>
になっています。
Dictionary
同士のキーが重複する場合、どちらを優先するか
keyValues.First()
ではdict1
が優先されkeyValues.Last()
にするとdict2
の値で上書きできます。
concated.GroupBy(
pair => pair.Key,
keyValues => keyValues.Last() // First -> Last
);
var dict1 = new Dictionary<string, int> {
["oarange"] = 100,
["apple"] = 150, // <- 重複
};
var dict2 = new Dictionary<string, int> {
["apple"] = 180, // <- 重複
["banana"] = 200,
};
key: oarange, value: 100
key: apple, value: 150 <- dict1 が優先
key: banana, value: 200
key: oarange, value: 100
key: apple, value: 180 <- dict2 が優先
key: banana, value: 200
KeyValuePair
のシーケンスをDictionary
化
Dictionary<string, int> mergedDict = pairs.ToDictionary(
(KeyValuePair<string, int> pair) => pair.Key,
(KeyValuePair<string, int> pair) => pair.Value
);
ToDictionary
は第一引数でキーを、第二引数にバリューをそれぞれラムダ式で指定できます。
拡張メソッド
for
文回して~ContainsKey
で重複判定して~と比べるとConcat
> GroupBy
> ToDicitonary
は短くなりますが、何度も書くのは面倒なので拡張メソッドを定義しておきます。
public static class IDictionaryExtentions {
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(
this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second
) => first.Concat(second)
.GroupBy(pair => pair.Key, (_, pairs) => pairs.First())
.ToDictionary(pair => pair.Key, pair => pair.Value);
public static IDictionary<TKey, TValue> Update<TKey, TValue>(
this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second
) => first.Concat(second)
.GroupBy(pair => pair.Key, (_, pairs) => pairs.Last())
.ToDictionary(pair => pair.Key, pair => pair.Value);
}
Merge
は重複するキーがある場合、呼出し側のDictionary
を優先します。
Update
は重複キーがある場合、引数で与えたDictionary
を優先します。
var dic1 = new Dictionary<string, int> {
["oarange"] = 100,
["apple"] = 150,
};
var dic2 = new Dictionary<string, int> {
["apple"] = 180,
["banana"] = 200,
};
var merged = dict1.Merge(dict2);
foreach (var (k, v) in merged) {
Console.WriteLine($"key: {k}, value: {v}");
}
/*
key: oarange, value: 100
key: apple, value: 150 <- 上書きされない
key: banana, value: 200
*/
var updated = dict1.Update(dict2);
foreach (var (k, v) in updated) {
Console.WriteLine($"key: {k}, value: {v}");
}
/*
key: oarange, value: 100
key: apple, value: 180 <- 上書きする
key: banana, value: 200
*/