2008-11-16 15 views
354

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

Ich denke an eine Methodensignatur entlang der Linien 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: Haben Sie eine coole Lösung von JaredPar und Jon Skeet, aber ich dachte, von etwas, das doppelte Schlüssel behandelt. Im Falle einer Kollision ist es egal, welcher Wert im Diktat gespeichert wird, solange es konsistent ist.

+131

Nicht verwandt, aber für jeden, der nur zwei Wörterbücher ohne doppelte Schlüsselüberprüfungen zusammenführen möchte, funktioniert dies gut: 'dicA.Concat (dicB). ToDictionary (kvp => kvp.Key, kvp => kvp.Value)' – Benjol

+4

@ Benjol, Sie könnten dies in Antwort Abschnitt hinzugefügt haben –

+3

Clojure Merge in C#: 'dict1.Concat (dict2) .GroupBy (p => p.Key). ToDictionary (g => g.Key, g => g.Last() .Wert) ' – Bruce

Antwort

218

Dies hängt zum Teil ab, was Sie wollen passieren, wenn Sie Duplikate laufen. Zum Beispiel könnten Sie tun:

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

Das wird in die Luft gehen, wenn Sie irgendwelche doppelte Schlüssel erhalten.

EDIT: Wenn Sie ToLookup verwenden, erhalten Sie eine Suche, die mehrere Werte pro Schlüssel haben kann. Sie könnte dann, dass 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, um es in Form von Code zu tun. (Ich habe es zugegebenermaßen nicht getestet.)

Sie könnten natürlich Ihre eigene ToDictionary2 Extension-Methode schreiben (mit einem besseren Namen, aber ich habe jetzt keine Zeit, an eine zu denken) - es ist nicht besonders schwer Do, überschreiben Sie einfach (oder ignorieren) doppelte Schlüssel. Das wichtige Bit (meiner Meinung nach) ist die Verwendung von SelectMany und die Erkenntnis, dass ein Dictionary eine Iteration über seine Schlüssel/Wert-Paare unterstützt.

+2

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

+0

+1: Super Lösung! Ich habe ".Sum()" in meiner Implementierung anstelle von ".First()" verwendet, da mein Wert eine ganze Zahl war, um die Summe der Werte über mehrere Wörterbücher mit doppelten Schlüsseln zu erhalten. – reSPAWNed

+3

Jetzt frage ich mich, dass GroupBy besser als ToLookup geeignet wäre? Ich denke, ILookup fügt einfach einen Indexer, eine Größeneigenschaft und eine Methode zusätzlich zu IGrouping hinzu - also wurde es ein kleines bisschen schneller? – toong

41

würde die triviale Lösung sein:

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

Versuchen Sie, die folgenden

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

sollte es tatsächlich sein: return enumerable.SelectMany ... –

5

Wie wäre es mit einer params Überladung?

Auch sollten Sie sie als IDictionary fü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); 
} 
+1

Schöne Schnittstelle :) – orip

+7

Downvoted, weil dies die Frage überhaupt nicht beantwortet. – Timwi

+4

Er fügt den anderen Antworten hier hinzu, indem er feststellt, dass Sie die DictionaryExtensions-Klasse noch netter machen können. Vielleicht sollte diese Frage zu einem Wiki werden, oder zu einer Klasse, die eingecheckt wird. –

8

Dies 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); 
     } 
    } 
} 
+0

Es gibt eine Menge toller Lösungen, die hier aufgelistet sind, aber ich mag eher die Einfachheit dieses hier. Nur meine persönliche Vorliebe. – jason

+3

'if (first == null)' dann ist diese Logik nutzlos, weil 'first' nicht' ref' ist und nicht zurückgegeben wird. Und es kann nicht "ref" sein, weil es als eine Schnittstelleninstanz deklariert ist; Sie müssen entweder die Fehlerprüfung entfernen oder sie als Klasseninstanz deklarieren oder einfach ein neues Wörterbuch (ohne Erweiterungsmethode) zurückgeben. – Grault

+0

@Jesdisciple - entfernt das Problem nach Ihrem Vorschlag –

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

Nur wundernd - würde nicht nur * Union * nur funktionieren? '' 'foreach (var kvp in Message.GetAttachments() .Union (mMessage.GetImages()))' '' Wenn Sie es im Produktionscode verwenden, lassen Sie es mich wissen, wenn es irgendwelche Nachteile gibt! :) –

+0

@Michal: Union wird ein 'IEnumerable > 'zurückgeben, wo die Gleichheit durch die Gleichheit von Schlüssel und Wert definiert wird (es gibt eine Überladung, um Alternativen zuzulassen). Das ist gut für eine foreach-Schleife, wo dies alles ist, was notwendig ist, aber es gibt Fälle, wo Sie das Wörterbuch tatsächlich wollen. – Timothy

88

Nun, ich bin spät zur Party, aber hier ist das, was ich benutze. Es explodiert nicht, wenn mehrere Schlüssel vorhanden sind ("righter" -Schlüssel ersetzen "lefter" -Schlüssel), können mehrere Wörterbücher zusammenführen (falls gewünscht) und den Typ beibehalten (mit der Einschränkung, dass ein aussagekräftiger öffentlicher Standardkonstruktor benötigt wird):

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

} 
+0

Gut gemacht. Das war genau das, was ich brauchte. Netter Gebrauch von Generika. Vereinbart, dass die Syntax ein wenig peinlich ist, aber nichts, was Sie dagegen tun können. –

+1

Ich mochte diese Lösung, aber es gibt einen Vorbehalt: Wenn das 'This T me' Wörterbuch mit' new Dictionary (StringComparer.InvariantCultureIgnoreCase) 'oder etwas Ähnlichem deklariert wurde, behält das resultierende Wörterbuch nicht das gleiche Verhalten bei. –

+3

Wenn Sie 'me' ein' Dictionary 'machen, können Sie' var newMap = neues Dictionary (me, me.Comparer); 'machen, und Sie vermeiden auch den zusätzlichen' T' type Parameter. – ANeves

161

ich würde es tun, wie folgt:

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

einfach und leicht.Laut this blog post ist es sogar schneller als die meisten Schleifen, da die zugrundeliegende Implementierung auf Elemente über den Index und nicht über den Enumerator (see this answer) zugreift.

Es wird natürlich eine Ausnahme werfen, wenn es Dubletten gibt, also müssen Sie vor dem Zusammenführen prüfen.

+105

Wenn Duplikate eine Rolle spielen, benutze 'dictionaryFrom.ToList(). ForEach (x => dictionaryTo [x .Key] = x.Wert) '. Auf diese Weise überschreiben die Werte von 'dictionaryFrom' den Wert für einen möglicherweise vorhandenen Schlüssel. – okrumnow

+5

Dies ist wahrscheinlich nicht schneller als eine Schleife - Sie vergessen, dass ToList() letztlich das Wörterbuch kopiert. Schneller zum Programmieren! ;-) –

+2

Das ist schneller .. Da ToList() tatsächlich nicht das Wörterbuch kopiert, kopiert es nur die Referenzen der Elemente in ihnen. Grundsätzlich hat das Listenobjekt selbst eine neue Adresse im Speicher, die Objekte sind gleich !! – GuruC

3

Basierend auf den Antworten oben, aber das Hinzufügen eines Func-Parameter der Anrufer behandeln die Duplikate zu lassen:

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

Die Partei ziemlich tot jetzt, aber hier ist eine „verbesserte“ Version von user166390, die aus seiner Weg in meine Erweiterungsbibliothek. 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; 
} 
1

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

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

Verbrauch:

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

Merging eine EqualityComparer verwenden, die Produkte zum Vergleich auf einen anderen Wert/Typ abbildet. Hier werden wir von KeyValuePair (Elementtyp beim Aufzählen eines Wörterbuchs) auf Key abbilden.

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

Verbrauch:

// 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); 
5

Ich bin sehr spät, um die Partei und vielleicht etwas fehlt, aber wenn entweder gibt es keine doppelten Schlüssel oder, wie die OP sagt: „Im Falle einer Kollision, Es spielt keine Rolle, welcher Wert im Diktat gespeichert wird, solange es konsistent ist. "Was ist falsch daran (D2 in D1 zusammenführen)?

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

Es scheint einfach genug, vielleicht zu einfach, ich frage mich, ob ich etwas vermisse. Dies ist, was ich in einem Code verwende, wo ich weiß, dass es keine doppelten Schlüssel gibt. Ich bin immer noch im Test, also würde ich gerne wissen, ob ich etwas übersehen habe, anstatt es später herauszufinden.

+0

Eine der saubersten und lesbarsten Lösungen imo und vermeidet auch die ToList(), die viele andere verwenden. – STDMP

13

Folgendes funktioniert für mich. Wenn Dubletten vorhanden sind, 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]); 
} 
+0

Dies funktioniert nicht mehr seit [] für Diktatwürfe. –

+0

@EsbenSkovPedersen Ich glaube nicht, dass ich angetroffen habe, als ich antwortete (neue C# Entwicklung) –

+0

jetzt sollte es funktionieren :) –

5

In Anbetracht der performance of dictionary key lookups and deletes da sie Hash-Operationen, und war die Formulierung der Frage unter Berücksichtigung besten Art und Weise, ich glaube, unten, dass ein absolut gültige Ansatz ist, und die anderen sind ein bisschen zu kompliziert, IMHO .

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 und das Wörterbuch muss arbeiten Thread-sicher sowieso sein, sollten Sie dies 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önnten dann diese wickeln, um es zu behandeln ein Aufzählung von Wörterbüchern. Unabhängig davon, Sie betrachten ~ O (3n) (alle Bedingungen sind perfekt), da die .Add() eine zusätzliche, unnötige, aber praktisch frei, Contains() hinter den Kulissen tun wird. Ich denke nicht, dass es viel besser wird.

Wenn Sie zusätzliche Operationen für große Sammlungen einschränken möchten, sollten Sie die Count jedes Wörterbuchs, das Sie zusammenführen möchten, zusammenfassen und die Kapazität des Zielwörterbuchs auf diese festlegen, wodurch die späteren Größenanpassungen vermieden werden. So ist Endprodukt so etwas wie dieses ...

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

Bitte beachte, dass ich in einem IList<T> dieser Methode nahm ... vor allem, weil, wenn Sie in einem IEnumerable<T> nehmen, können Sie sich geöffnet haben bis zu mehreren Aufzählungen Dieselbe Menge, die sehr teuer sein kann, wenn Sie Ihre Sammlung von Wörterbüchern aus einer zurückgestellten LINQ-Anweisung erhalten.

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 zur Lösung von @ Andrew Orsich aufgebracht, so dass die MergeLeft wie folgt aussieht jetzt:

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

In der Nähe von dem, was ich selbst geschrieben habe. Dieses Ofc funktioniert nur für Dictionary-Typen. Überladungen könnten für andere Dictionary-Typen hinzugefügt werden ('ConcurrentDictionary',' ReadOnlyDictionary', usw.) Ich denke nicht, dass die Erstellung einer neuen Liste und Concat persönlich notwendig ist. Ich habe das params-Array zuerst iteriert und dann jedes KVP jedes Wörterbuchs wiederholt. Ich sehe keinen Rückzug. – crush

+0

Sie können IDictionary anstelle von Dictionary verwenden –

+0

Sie würden immer ein Dictionary-Objekt erhalten, selbst wenn Sie die Erweiterungsmethode für ein ConcurrentDictionary verwendet haben. Das könnte dazu führen, dass Bugs nur schwer zu finden sind. – Marcel

3

Ich weiß, dass dies eine alte Frage, aber da wir jetzt haben LINQ Sie es in einer einzigen Zeile wie diese

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)); 
tun können
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, wo für doppelte Einträge "y" gewinnt.

Verwandte Themen