Zusammenführen von Wörterbüchern in C #


493

Was ist der beste Weg, um zwei oder mehr Wörterbücher zusammenzuführen (Dictionary<T1,T2> ) in C # zusammenzuführen? (3.0 Funktionen wie LINQ sind in Ordnung).

Ich denke an eine Methodensignatur im Sinne von:

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

oder

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

EDIT: Ich habe eine coole Lösung von JaredPar und Jon Skeet bekommen, aber ich habe an etwas gedacht, das doppelte Schlüssel handhabt. Im Falle einer Kollision spielt es keine Rolle, welcher Wert im Diktat gespeichert wird, solange er konsistent ist.


174
Unabhängig, aber für alle, die nur zwei Wörterbücher ohne doppelte Schlüsselüberprüfung zusammenführen dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
möchten

6
@ Benjol Sie könnten dies im Antwortbereich hinzugefügt haben
M. Kumararan

4
Clojures Fusion in C #:dict1.Concat(dict2).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value)
Bruce

@Benjol: Beseitigen Sie Duplikate, indem Sie Einträge aus dem ersten Wörterbuch mit bevorzugen dictA.Concat(dictB.Where(kvp => !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=> kvp.Key, kvp => kvp.Value).
Suncat2000

Antworten:


321

Dies hängt teilweise davon ab, was passieren soll, wenn Sie auf Duplikate stoßen. Zum Beispiel könnten Sie tun:

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

Dies löst eine Ausnahme aus, wenn Sie doppelte Schlüssel erhalten.

BEARBEITEN: Wenn Sie ToLookup verwenden, erhalten Sie eine Suche, die mehrere Werte pro Schlüssel haben kann. Sie könnten das dann in ein Wörterbuch konvertieren:

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

Es ist ein bisschen hässlich - und ineffizient - aber es ist der schnellste Weg, es in Bezug auf Code zu tun. (Ich habe es zugegebenermaßen nicht getestet.)

Sie könnten natürlich Ihre eigene ToDictionary2-Erweiterungsmethode schreiben (mit einem besseren Namen, aber ich habe jetzt keine Zeit, mir eine auszudenken) - es ist nicht besonders schwer, doppelte Schlüssel zu überschreiben (oder zu ignorieren). Das Wichtigste (meiner Meinung nach) ist die Verwendung von SelectMany und die Erkenntnis, dass ein Wörterbuch die Iteration über seine Schlüssel / Wert-Paare unterstützt.


3
Um die Werte tatsächlich zusammenzuführen, anstatt sie nur aus dem ersten Wörterbuch zu übernehmen, können Sie group => group.First () durch group => group.SelectMany (value => value) in Jon Skeets bearbeiteter Antwort ersetzen.
andreialecu

3
Jetzt frage ich mich, ob GroupBy besser geeignet wäre als ToLookup? Ich denke, ILookup fügt nur einen Indexer, eine Size-Eigenschaft und eine Methode zusätzlich zu IGrouping hinzu - also muss es ein bisschen schneller sein?
Toong

2
@toong: Beides sollte in Ordnung sein, um ehrlich zu sein. Die Verwendung GroupBykönnte zwar etwas effizienter sein - aber ich bezweifle, dass dies in beiden Fällen von Bedeutung ist.
Jon Skeet

1
@ Joe: Das ist einfach genug, indem der Vergleicher zum ToDictionaryMethodenaufruf bereitgestellt wird . Ich würde es jedoch vorziehen, die Antwort für den häufigeren Fall einfacher zu halten, und ich hoffe, dass jeder, der einen benutzerdefinierten Vergleicher benötigt, in der Lage ist, die relevante Überlastung zu finden.
Jon Skeet

1
@ Serge: Ich schlage vor, Sie stellen eine neue Frage mit genauen Anforderungen.
Jon Skeet

265

Ich würde es so machen:

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

Simpel und einfach. Laut diesem Blog-Beitrag ist es sogar schneller als die meisten Schleifen, da die zugrunde liegende Implementierung eher über den Index als über den Enumerator auf Elemente zugreift (siehe diese Antwort) .

Es wird natürlich eine Ausnahme auslösen, wenn es Duplikate gibt, daher müssen Sie dies vor dem Zusammenführen überprüfen.


169
Wenn Duplikate wichtig sind, verwenden Sie dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value). Auf diese Weise dictionaryFromüberschreiben die Werte von den Wert für einen möglicherweise vorhandenen Schlüssel.
Okrumnow

7
Dies ist wahrscheinlich nicht schneller als eine Schleife - Sie vergessen, dass ToList () tatsächlich das Wörterbuch kopiert. Schneller zu codieren! ;-)
Søren Boisen

6
Dies ist schneller. Da ToList () das Wörterbuch tatsächlich nicht kopiert, werden nur die Referenzen der darin enthaltenen Elemente kopiert. Grundsätzlich hat das Listenobjekt selbst eine neue Adresse im Speicher, die Objekte sind gleich !!
GuruC

4
Blog-Beitrag verschwunden, aber yay Wayback-Maschine: web.archive.org/web/20150311191313/http://diditwith.net/2006/10/…
CAD-Kerl

1
Hmm, besser als die Antwort von Jon Skeet, nehme ich an.
Sнаđошƒаӽ

102

Dies explodiert nicht, wenn mehrere Schlüssel vorhanden sind ("rechte" Schlüssel ersetzen "lefter" Schlüssel), kann eine Reihe von Wörterbüchern zusammenführen (falls gewünscht) und behält den Typ bei (mit der Einschränkung, dass ein aussagekräftiger öffentlicher Standardkonstruktor erforderlich ist):

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;
    }

}

Gut gemacht. Das war genau das, was ich brauchte. Gute Verwendung von Generika. Einverstanden, dass die Syntax etwas umständlich ist, aber nichts, was Sie dagegen tun können.
Peter M

2
Ich mochte diese Lösung, aber es gibt eine Einschränkung: Wenn das this T meWörterbuch mit new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase)oder ähnlich deklariert wurde, behält das resultierende Wörterbuch nicht das gleiche Verhalten bei.
João Portela

3
Wenn Sie machen meein Dictionary<K,V>, können Sie tun var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);, und Sie haben auch den zusätzlichen vermeiden TTypparameter.
Aneves

1
Hat jemand ein Beispiel, wie man dieses Biest benutzt?
Tim

1
@ Tim: Ich habe ein Beispiel für das Tier unten hinzugefügt, ich habe die Lösung von ANeves hinzugefügt und das Ergebnis ist ein domestizierteres Tier :)
Keni

50

Die triviale Lösung wäre:

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;
}

22

Versuche Folgendes

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);
}

19
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

1
Ich frage mich nur - würde Union nicht einfach funktionieren? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages()))Wenn es Nachteile im Produktionscode gibt, lassen Sie es mich bitte wissen! :)
Mars Robertson

1
@Michal: Union wird eine Rückgabe zurückgeben, IEnumerable<KeyValuePair<TKey, TValue>>bei der Gleichheit durch Gleichheit von Schlüssel und Wert definiert wird (es gibt eine Überlastung, um Alternativen zuzulassen). Dies ist in Ordnung für eine foreach-Schleife, in der dies alles ist, was erforderlich ist, aber es gibt Fälle, in denen Sie das Wörterbuch tatsächlich möchten.
Timothy

15

Ich bin sehr spät zur Party und vermisse vielleicht etwas, aber wenn entweder keine doppelten Schlüssel vorhanden sind oder, wie das OP sagt: "Im Falle einer Kollision spielt es keine Rolle, welcher Wert für das Diktat gespeichert wird, solange es ist konsistent: "Was ist los mit diesem (Zusammenführen von D2 zu D1)?

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

Es scheint einfach genug, vielleicht zu einfach, ich frage mich, ob mir etwas fehlt. Dies ist, was ich in einem Code verwende, in dem ich weiß, dass es keine doppelten Schlüssel gibt. Ich bin jedoch noch im Testen, daher würde ich jetzt gerne wissen, ob ich etwas übersehen habe, anstatt es später herauszufinden.


4
Eine der saubersten und am besten lesbaren Lösungen imo und vermeidet auch die ToList (), die viele andere verwenden.
404

15

Folgendes funktioniert für mich. Wenn es Duplikate gibt, wird der Wert von dictA verwendet.

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]);
}

@EsbenSkovPedersen Ich glaube nicht, dass ich darauf gestoßen bin, als ich darauf antwortete (neuer C # dev)
Ethan Reesor

Ich musste 'where TValue: class' entfernen, um Guid als Wert zu verwenden
om471987

@ om471987 Ja, Guid ist eine Struktur, keine Klasse, das macht also Sinn. Ich glaube, ursprünglich hatte ich ein Problem, als ich diese Klausel nicht hinzufügte. Ich erinnere mich nicht mehr warum.
Ethan Reesor

10

Hier ist eine Hilfsfunktion, die ich benutze:

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);
        }
    }
}

Es gibt viele großartige Lösungen, die hier aufgelistet sind, aber ich mag die Einfachheit dieser am meisten. Nur meine persönliche Präferenz.
Jason

3
if(first == null)dann ist diese Logik nutzlos , da firstist nicht refund wird nicht zurückgegeben. Und es kann nicht sein, refdass es als Schnittstelleninstanz deklariert ist. Sie müssen entweder die Fehlerprüfung entfernen oder als Klasseninstanz deklarieren oder einfach ein neues Wörterbuch zurückgeben (ohne Erweiterungsmethode).
Grault

@Jesdisciple - entfernte das Problem gemäß Ihrem Vorschlag
Andrew Harry

8

Option 1: Dies hängt davon ab, was passieren soll, wenn Sie sicher sind, dass Sie in beiden Wörterbüchern keinen doppelten Schlüssel haben. als du tun könntest:

var result = dictionary1.Union(dictionary2).ToDictionary(k => k.Key, v => v.Value)

Hinweis : Dies führt zu Fehlern, wenn Sie doppelte Schlüssel in Wörterbüchern erhalten.

Option 2: Wenn Sie einen doppelten Schlüssel haben können, müssen Sie den doppelten Schlüssel mit der where-Klausel behandeln.

var result = dictionary1.Union(dictionary2.Where(k => !dictionary1.ContainsKey(k.Key))).ToDictionary(k => k.Key, v => v.Value)

Hinweis: Es wird kein doppelter Schlüssel erhalten. Wenn es einen doppelten Schlüssel gibt, erhält er den Schlüssel von dictionary1.

Option 3: Wenn Sie ToLookup verwenden möchten. Dann erhalten Sie eine Suche, die mehrere Werte pro Schlüssel haben kann. Sie können diese Suche in ein Wörterbuch konvertieren:

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

6

Wie wäre es mit einer paramsÜberladung?

Außerdem sollten Sie sie IDictionaryfür maximale Flexibilität eingeben.

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);
}

4
Er ergänzt die anderen Antworten hier, indem er feststellt, dass Sie die DictionaryExtensions-Klasse noch schöner machen können. Vielleicht sollte diese Frage ein Wiki werden oder eine Klasse, die bei git eingecheckt ist ....
Chris Moschini

5

In Anbetracht der Leistung beim Suchen und Löschen von Wörterbuchschlüsseln, da es sich um Hash-Operationen handelt, und angesichts des Wortlauts der Frage, der der beste Weg war, denke ich, dass das Folgende ein vollkommen gültiger Ansatz ist und die anderen meiner Meinung nach etwas zu kompliziert sind.

    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);
        }
    }

ODER wenn Sie in einer Multithread-Anwendung arbeiten und Ihr Wörterbuch ohnehin threadsicher sein muss, sollten Sie Folgendes tun:

    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);
        }
    }

Sie können dies dann umbrechen, um eine Aufzählung von Wörterbüchern zu verarbeiten. Unabhängig davon sehen Sie ungefähr ~ O (3n) (alle Bedingungen sind perfekt), da die .Add()einen zusätzlichen, unnötigen, aber praktisch kostenlosen,Contains() hinter die Kulissen werfen. Ich denke nicht, dass es viel besser wird.

Wenn Sie zusätzliche Vorgänge für große Sammlungen einschränken möchten, sollten CountSie die Größe jedes Wörterbuchs, das Sie zusammenführen möchten, zusammenfassen und die Kapazität des Zielwörterbuchs auf diese festlegen, wodurch die späteren Kosten für die Größenänderung vermieden werden. Das Endprodukt ist also ungefähr so ​​...

    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;
    }

Beachten Sie, dass ich mich IList<T>für diese Methode entschieden habe ... hauptsächlich, weil Sie sich bei der Aufnahme einer Methode IEnumerable<T>für mehrere Aufzählungen desselben Satzes geöffnet haben. Dies kann sehr kostspielig sein, wenn Sie Ihre Sammlung von Wörterbüchern von einem zurückgestellten LINQ erhalten haben Aussage.


4

Basierend auf den obigen Antworten, aber Hinzufügen eines Func-Parameters, damit der Aufrufer die Duplikate verarbeiten kann:

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));
}

4

Die Party ist jetzt ziemlich tot, aber hier ist eine "verbesserte" Version von user166390, die ihren Weg in meine Erweiterungsbibliothek gefunden hat. Abgesehen von einigen Details habe ich einen Delegaten hinzugefügt, um den zusammengeführten Wert zu berechnen.

/// <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;
}

2

@Tim: Sollte ein Kommentar sein, aber Kommentare erlauben keine Code-Bearbeitung.

Dictionary<string, string> t1 = new Dictionary<string, string>();
t1.Add("a", "aaa");
Dictionary<string, string> t2 = new Dictionary<string, string>();
t2.Add("b", "bee");
Dictionary<string, string> t3 = new Dictionary<string, string>();
t3.Add("c", "cee");
t3.Add("d", "dee");
t3.Add("b", "bee");
Dictionary<string, string> merged = t1.MergeLeft(t2, t2, t3);

Hinweis: Ich habe die Änderung von @ANeves auf die Lösung von @Andrew Orsich angewendet, sodass MergeLeft jetzt folgendermaßen aussieht:

public static Dictionary<K, V> MergeLeft<K, V>(this Dictionary<K, V> me, params IDictionary<K, V>[] others)
    {
        var newMap = new Dictionary<K, V>(me, me.Comparer);
        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;
    }

Nah an dem, was ich selbst geschrieben habe. Dies funktioniert nur für DictionaryTypen. Überladungen könnten für andere Wörterbuchtypen ( ConcurrentDictionary, ReadOnlyDictionaryusw.) hinzugefügt werden. Ich denke nicht, dass die Erstellung einer neuen Liste und concat persönlich erforderlich ist. Ich habe gerade zuerst das params-Array iteriert und dann jede KVP jedes Wörterbuchs iteriert. Ich sehe keinen Nachteil.
Crush

Sie können IDictionary anstelle von Dictionary
Mariusz Jamro

Sie würden immer ein DictionaryObjekt erhalten, selbst wenn Sie die Erweiterungsmethode für a verwenden würden ConcurrentDictionary. Das könnte zu schwer auffindbaren Fehlern führen.
Marcel

2

Ich weiß, dass dies eine alte Frage ist, aber da wir jetzt LINQ haben, können Sie dies in einer einzigen Zeile wie dieser tun

Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));

oder

mergee.ToList().ForEach(kvp => merged.Append(kvp));

Entschuldigen Sie die Abwertung von @Cruces, aber Ihr erstes Beispiel dupliziert die Antwort stackoverflow.com/a/6695211/704808 und Ihr zweites Beispiel verwirft die erweiterte IEnumerable <KeyValuePair <Zeichenfolge, Zeichenfolge >> nach dem Anhängen jedes Elements von mergee.
Wehr

2

Ich hatte Angst, komplexe Antworten zu sehen, weil ich neu in C # war.

Hier sind einige einfache Antworten.
Zusammenführen von d1, d2 usw. Wörterbüchern und Behandeln überlappender Schlüssel ("b" in den folgenden Beispielen):

Beispiel 1

{
    // 2 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };

    var result1 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=22, c=30    That is, took the "b" value of the last dictionary
}

Beispiel 2

{
    // 3 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };
    var d3 = new Dictionary<string, int>() { { "d", 40 }, { "b", 23 } };

    var result1 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30, d=40    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=23, c=30, d=40    That is, took the "b" value of the last dictionary
}

Für komplexere Szenarien siehe andere Antworten.
Hoffe das hat geholfen.


2
using System.Collections.Generic;
using System.Linq;

public static class DictionaryExtensions
{
    public enum MergeKind { SkipDuplicates, OverwriteDuplicates }
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, MergeKind kind = MergeKind.SkipDuplicates) =>
        source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });
}

Sie können die Duplikate entweder überspringen / ignorieren (Standard) oder überschreiben: Und Bob ist Ihr Onkel, vorausgesetzt, Sie sind nicht übermäßig pingelig in Bezug auf die Linq-Leistung, sondern bevorzugen einen präzisen, wartbaren Code wie ich: In diesem Fall können Sie die Standard-MergeKind.SkipDuplicates entfernen, um sie zu erzwingen eine Wahl für den Anrufer und machen Sie den Entwickler auf die Ergebnisse aufmerksam!


Verfeinerte Antwort unten ohne unnötige binäre Aufzählung, wenn ein Bool tun wird ...
Mattjs

2

Beachten Sie, dass Sie bei Verwendung einer Erweiterungsmethode namens "Hinzufügen" Sammlungsinitialisierer verwenden können, um so viele Wörterbücher wie nötig wie folgt zu kombinieren:

public static void Add<K, V>(this Dictionary<K, V> d, Dictionary<K, V> other) {
  foreach (var kvp in other)
  {
    if (!d.ContainsKey(kvp.Key))
    {
      d.Add(kvp.Key, kvp.Value);
    }
  }
}


var s0 = new Dictionary<string, string> {
  { "A", "X"}
};
var s1 = new Dictionary<string, string> {
  { "A", "X" },
  { "B", "Y" }
};
// Combine as many dictionaries and key pairs as needed
var a = new Dictionary<string, string> {
  s0, s1, s0, s1, s1, { "C", "Z" }
};

1

Zusammenführen mit einer Erweiterungsmethode. Es wird keine Ausnahme ausgelöst, wenn doppelte Schlüssel vorhanden sind, sondern diese Schlüssel werden durch Schlüssel aus dem zweiten Wörterbuch ersetzt.

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;
    }
}

Verwendungszweck:

Dictionary<string, string> merged = first.Merge(second);

1

Vereinfacht von der Verwendung im Vergleich zu meiner früheren Antwort mit einem Bool-Standardwert für zerstörungsfreie Zusammenführung, wenn vorhanden, oder vollständig überschreiben, wenn wahr, anstatt eine Aufzählung zu verwenden. Es entspricht immer noch meinen eigenen Bedürfnissen, ohne dass jemals ein schickerer Code benötigt wird:

using System.Collections.Generic;
using System.Linq;

public static partial class Extensions
{
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, bool overwrite = false)
    {
        source.ToList().ForEach(_ => {
            if ((!target.ContainsKey(_.Key)) || overwrite)
                target[_.Key] = _.Value;
        });
    }
}

0

Zusammenführen mit einem EqualityComparer, der Elemente zum Vergleich mit einem anderen Wert / Typ abbildet. Hier werden wir von KeyValuePair(Elementtyp beim Aufzählen eines Wörterbuchs) auf zuordnen Key.

public class MappedEqualityComparer<T,U> : EqualityComparer<T>
{
    Func<T,U> _map;

    public MappedEqualityComparer(Func<T,U> map)
    {
        _map = map;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityComparer<U>.Default.Equals(_map(x), _map(y));
    }

    public override int GetHashCode(T obj)
    {
        return _map(obj).GetHashCode();
    }
}

Verwendungszweck:

// if dictA and dictB are of type Dictionary<int,string>
var dict = dictA.Concat(dictB)
                .Distinct(new MappedEqualityComparer<KeyValuePair<int,string>,int>(item => item.Key))
                .ToDictionary(item => item.Key, item=> item.Value);

0

oder :

public static IDictionary<TKey, TValue> Merge<TKey, TValue>( IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        return x
            .Except(x.Join(y, z => z.Key, z => z.Key, (a, b) => a))
            .Concat(y)
            .ToDictionary(z => z.Key, z => z.Value);
    }

Das Ergebnis ist eine Vereinigung, bei der bei doppelten Einträgen "y" gewinnt.


0
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;
        }

0

Eine Version von @ user166390 antwortet mit einem zusätzlichen IEqualityComparerParameter, um einen Schlüsselvergleich ohne Berücksichtigung der Groß- und Kleinschreibung zu ermöglichen.

    public static T MergeLeft<T, K, V>(this T me, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        return me.MergeLeft(me.Comparer, others);
    }

    public static T MergeLeft<T, K, V>(this T me, IEqualityComparer<K> comparer, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        T newMap = Activator.CreateInstance(typeof(T), new object[] { comparer }) as T;

        foreach (Dictionary<K, V> src in 
            (new List<Dictionary<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;
    }

0
fromDic.ToList().ForEach(x =>
        {
            if (toDic.ContainsKey(x.Key))
                toDic.Remove(x.Key);
            toDic.Add(x);
        });
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.