586

Dictionary<T1,T2>C#で2つ以上の辞書()をマージする最良の方法は何ですか? (LINQ のような 3.0 機能は問題ありません)。

私は次の行に沿ったメソッド署名を考えています:

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);

また

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);

編集: JaredPar と Jon Skeet からクールな解決策を得ましたが、重複したキーを処理するものを考えていました。衝突が発生した場合、一貫性がある限り、どの値が dict に保存されるかは問題ではありません。

4

29 に答える 29

368

これは、重複に遭遇した場合に何をしたいかによって部分的に異なります。たとえば、次のことができます。

var result = dictionaries.SelectMany(dict => dict)
                         .ToDictionary(pair => pair.Key, pair => pair.Value);

重複したキーを取得すると、例外がスローされます。

編集: ToLookup を使用すると、キーごとに複数の値を持つことができるルックアップが得られます。次に、それを辞書に変換できます。

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

これは少し見栄えが悪く、非効率的ですが、コードの観点からは、これが最も手っ取り早い方法です。(確かに、私はそれをテストしていません。)

もちろん、独自の ToDictionary2 拡張メソッドを作成することもできます (より適切な名前を使用しますが、今は考える時間はありません)。重複キーを上書き (または無視) するだけで、それほど難しくはありません。(私の考えでは) 重要な点はSelectMany、 を使用していることと、辞書がキーと値のペアの反復をサポートしていることを認識していることです。

于 2008-11-16T17:46:11.527 に答える
319

私は次のようにします:

dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));

シンプルで簡単。このブログ投稿によると、基になる実装が列挙子ではなくインデックスで要素にアクセスするため、ほとんどのループよりもさらに高速です(この回答を参照)

もちろん、重複がある場合は例外がスローされるため、マージする前に確認する必要があります。

于 2011-07-14T14:51:20.647 に答える
113

複数のキーが存在する場合 (「右側の」キーが「左側の」キーを置き換えます)、これは爆発せず、(必要に応じて) 多数の辞書をマージし、型を保持します (意味のある既定のパブリック コンストラクターが必要であるという制限があります)。

public static class DictionaryExtensions
{
    // Works in C#3/VS2008:
    // Returns a new dictionary of this ... others merged leftward.
    // Keeps the type of 'this', which must be default-instantiable.
    // Example: 
    //   result = map.MergeLeft(other1, other2, ...)
    public static T MergeLeft<T,K,V>(this T me, params IDictionary<K,V>[] others)
        where T : IDictionary<K,V>, new()
    {
        T newMap = new T();
        foreach (IDictionary<K,V> src in
            (new List<IDictionary<K,V>> { me }).Concat(others)) {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K,V> p in src) {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

}
于 2010-04-21T02:05:31.717 に答える
56

些細な解決策は次のとおりです。

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
    Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var dict in dictionaries)
        foreach (var x in dict)
            result[x.Key] = x.Value;
    return result;
}
于 2008-11-16T17:40:23.037 に答える
27

以下を試してください

static Dictionary<TKey, TValue>
    Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> enumerable)
{
    return enumerable.SelectMany(x => x).ToDictionary(x => x.Key, y => y.Value);
}
于 2008-11-16T17:44:12.547 に答える
22
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
于 2010-04-06T15:28:54.353 に答える
21

私はパーティーに非常に遅れており、おそらく何かが欠けていますが、重複するキーがない場合、または OP が言うように、「衝突の場合、どの値が dict に保存されているかは問題ではありません。一貫して、"これ (D2 を D1 にマージ) の何が問題なのですか?

foreach (KeyValuePair<string,int> item in D2)
{
    D1[item.Key] = item.Value;
}

シンプルすぎるかもしれませんが、何かが足りないのではないかと思います。これは、重複キーがないことがわかっているコードで使用しているものです。ただし、私はまだテスト中なので、何か見落としがある場合は、後で見つけるのではなく、今すぐ知りたいと思っています.

于 2014-07-08T05:27:59.293 に答える
16

以下は私にとってはうまくいきます。重複がある場合は、dictA の値が使用されます。

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> dictA, IDictionary<TKey, TValue> dictB)
    where TValue : class
{
    return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);
}
于 2014-08-08T22:22:07.893 に答える
15

これが私が使用するヘルパー関数です:

using System.Collections.Generic;
namespace HelperMethods
{
    public static class MergeDictionaries
    {
        public static void Merge<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
        {
            if (second == null || first == null) return;
            foreach (var item in second) 
                if (!first.ContainsKey(item.Key)) 
                    first.Add(item.Key, item.Value);
        }
    }
}
于 2009-08-06T04:26:12.620 に答える
7

paramsオーバーロードを追加するのはどうですか?

また、IDictionary最大限の柔軟性を得るために、それらを入力する必要があります。

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(IEnumerable<IDictionary<TKey, TValue>> dictionaries)
{
    // ...
}

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(params IDictionary<TKey, TValue>[] dictionaries)
{
    return Merge((IEnumerable<TKey, TValue>) dictionaries);
}
于 2008-11-16T17:55:53.567 に答える
6

上記の回答に基づいていますが、 Func-parameter を追加して、呼び出し元が重複を処理できるようにします。

public static Dictionary<TKey, TValue> Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> dicts, 
                                                           Func<IGrouping<TKey, TValue>, TValue> resolveDuplicates)
{
    if (resolveDuplicates == null)
        resolveDuplicates = new Func<IGrouping<TKey, TValue>, TValue>(group => group.First());

    return dicts.SelectMany<Dictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(dict => dict)
                .ToLookup(pair => pair.Key, pair => pair.Value)
                .ToDictionary(group => group.Key, group => resolveDuplicates(group));
}
于 2013-02-20T10:28:25.497 に答える
5

ハッシュ操作であるため、辞書キーの検索と削除のパフォーマンスを考慮し、質問の文言が最善の方法であると考えると、以下は完全に有効なアプローチであり、他のアプローチは少し複雑すぎると思います。

    public static void MergeOverwrite<T1, T2>(this IDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null) return;

        foreach (var e in newElements)
        {
            dictionary.Remove(e.Key); //or if you don't want to overwrite do (if !.Contains()
            dictionary.Add(e);
        }
    }

または、マルチスレッド アプリケーションで作業していて、とにかくディクショナリをスレッド セーフにする必要がある場合は、次のようにする必要があります。

    public static void MergeOverwrite<T1, T2>(this ConcurrentDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null || newElements.Count == 0) return;

        foreach (var ne in newElements)
        {
            dictionary.AddOrUpdate(ne.Key, ne.Value, (key, value) => value);
        }
    }

次に、これをラップして、辞書の列挙を処理できるようにします。とにかく、約 ~O(3n) (すべての条件が完璧な場合) を.Add()Contains()ています。あまり良くならないと思います。

大規模なコレクションで余分な操作を制限したい場合はCount、マージしようとしている各ディクショナリを合計し、ターゲット ディクショナリの容量をそれに設定する必要があります。これにより、後のサイズ変更のコストが回避されます。だから、最終製品はこのようなものです...

    public static IDictionary<T1, T2> MergeAllOverwrite<T1, T2>(IList<IDictionary<T1, T2>> allDictionaries)
    {
        var initSize = allDictionaries.Sum(d => d.Count);
        var resultDictionary = new Dictionary<T1, T2>(initSize);
        allDictionaries.ForEach(resultDictionary.MergeOverwrite);
        return resultDictionary;
    }

このメソッドに を取り入れたことに注意してくださいIList<T>...ほとんどの場合、 を取り入れた場合IEnumerable<T>、同じセットの複数の列挙に自分自身を開いたためです。これは、遅延 LINQ から辞書のコレクションを取得した場合、非常にコストがかかる可能性があります。声明。

于 2014-10-13T19:44:23.213 に答える
5

パーティーは今ではほとんど死んでいますが、これが私の拡張ライブラリに組み込まれた user166390 の「改善された」バージョンです。いくつかの詳細とは別に、マージされた値を計算するデリゲートを追加しました。

/// <summary>
/// Merges a dictionary against an array of other dictionaries.
/// </summary>
/// <typeparam name="TResult">The type of the resulting dictionary.</typeparam>
/// <typeparam name="TKey">The type of the key in the resulting dictionary.</typeparam>
/// <typeparam name="TValue">The type of the value in the resulting dictionary.</typeparam>
/// <param name="source">The source dictionary.</param>
/// <param name="mergeBehavior">A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)</param>
/// <param name="mergers">Dictionaries to merge against.</param>
/// <returns>The merged dictionary.</returns>
public static TResult MergeLeft<TResult, TKey, TValue>(
    this TResult source,
    Func<TKey, TValue, TValue, TValue> mergeBehavior,
    params IDictionary<TKey, TValue>[] mergers)
    where TResult : IDictionary<TKey, TValue>, new()
{
    var result = new TResult();
    var sources = new List<IDictionary<TKey, TValue>> { source }
        .Concat(mergers);

    foreach (var kv in sources.SelectMany(src => src))
    {
        TValue previousValue;
        result.TryGetValue(kv.Key, out previousValue);
        result[kv.Key] = mergeBehavior(kv.Key, kv.Value, previousValue);
    }

    return result;
}
于 2013-04-11T20:00:23.893 に答える
1

拡張メソッドを使用したマージ。重複するキーがある場合は例外をスローしませんが、それらのキーを 2 番目の辞書のキーに置き換えます。

internal static class DictionaryExtensions
{
    public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
    {
        if (first == null) throw new ArgumentNullException("first");
        if (second == null) throw new ArgumentNullException("second");

        var merged = new Dictionary<T1, T2>();
        first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
        second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);

        return merged;
    }
}

使用法:

Dictionary<string, string> merged = first.Merge(second);
于 2013-11-08T12:35:35.797 に答える
1
public static IDictionary<K, V> AddRange<K, V>(this IDictionary<K, V> one, IDictionary<K, V> two)
        {
            foreach (var kvp in two)
            {
                if (one.ContainsKey(kvp.Key))
                    one[kvp.Key] = two[kvp.Key];
                else
                    one.Add(kvp.Key, kvp.Value);
            }
            return one;
        }
于 2019-04-12T16:26:06.117 に答える
0

これが私の解決策ですdict.update()。Python のメソッドのように動作します。

public static class DictionaryExtensions
{
    public static void Update<K,V>(this IDictionary<K, V> me, IDictionary<K, V> other)
    {
        foreach (var x in other)
        {
            me[x.Key] = x.Value;
        }
    }
}
于 2022-01-20T15:23:58.740 に答える
0

@orip のシンプルでガベージを作成しないソリューションを分割して、ある辞書を別の辞書に追加するという単純なケースを処理するAddAll()だけでなく、インプレースを提供します。Merge()

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
    AddAll<TKey,TValue>(Dictionary<TKey, TValue> dest, Dictionary<TKey, TValue> source)
{
    foreach (var x in source)
        dest[x.Key] = x.Value;
}

public static Dictionary<TKey, TValue>
    Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var dict in dictionaries)
        result.AddAll(dict);
    return result;
}
于 2021-05-17T18:36:44.870 に答える