2008-11-16 3 views
354

Каков наилучший способ слияния 2 или более словарей (Dictionary<T1,T2>) в C#? (3,0 функции, такие как LINQ, являются точными).Слияние словарей в C#

Я имею в виду сигнатуры метода по линиям:

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

EDIT: Есть прохладное решение от JaredPar и Джон тарелочкам, но я думал, что-то, что обрабатывает дубликаты ключей. В случае столкновения не имеет значения, какое значение сохраняется в dict, если оно согласовано.

+131

Несвязанный, но для тех, кто хочет слить только два словаря без повторных проверок ключей, это прекрасно работает: `dicA.Concat (dicB) .ToDictionary (kvp => kvp.Key, kvp => kvp.Value)` – Benjol 2011-12-14 07:31:42

+4

@ Benjol, вы могли бы добавить это в раздел ответа – 2013-09-02 13:54:56

+3

Слияние Clojure в C#: `dict1.Concat (dict2) .GroupBy (p => p.Key) .ToDictionary (g => g.Key, g => g.Last() .Value) ` – Bruce 2015-06-01 04:00:34

ответ

218

Это отчасти зависит от того, что вы хотите, чтобы произошло, если вы столкнетесь с дубликатами. Например, вы можете:

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

Это будет взорваться, если вы получите дубликаты ключей.

EDIT: Если вы используете ToLookup, вы получите поиск, который может иметь несколько значений для каждого ключа. Вы могли затем преобразовать его в словарь:

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

Это немного некрасиво - и неэффективный - но это самый быстрый способ сделать это с точки зрения кода. (Я не проверял это, по общему признанию.)

Вы можете написать свой собственный метод расширения ToDictionary2, конечно (с лучшим именем, но у меня нет времени подумать об этом сейчас) - это не очень сложно do, просто переписывая (или игнорируя) дубликаты ключей. Важный бит (на мой взгляд) использует SelectMany и понимает, что словарь поддерживает итерацию по своим парам ключ/значение.

+2

Чтобы фактически объединить значения вместо того, чтобы просто взять их из первого словаря, вы можете заменить group => group.First() с группой => group.SelectMany (значение => значение) в отредактированном ответе Джона Скита. – andreialecu 2011-04-13 15:53:25

+0

+1: Удивительное решение! Я использовал «.Sum()» в моей реализации вместо «.First()», так как мое значение было целым числом, чтобы получить общее количество значений через пару словарей, содержащих дубликаты ключей. – reSPAWNed 2013-05-10 12:28:41

+3

Теперь мне интересно, что GroupBy лучше подходит, чем ToLookup? Я думаю, что ILookup просто добавляет свойство indexer, size и содержит метод поверх IGrouping - так что это должно быть немного быстрее? – toong 2013-05-30 15:25:06

41

Нулевое решение будет:

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

Попробуйте следующее

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

это должно быть: return enumerable.SelectMany ... – 2010-07-01 01:41:01

5

Как насчет добавления перегрузки 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); 
} 
+1

Nice interface :) – orip 2008-11-17 07:56:17

8

Вот функция помощника я использую:

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); 
     } 
    } 
} 
17
Dictionary<String, String> allTables = new Dictionary<String, String>(); 
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value); 
88

Ну, я опоздал на вечеринку, но вот то, что я использую. Он не взрывается, если есть несколько ключей («более быстрые» клавиши заменяют «lefter»), могут объединять несколько словарей (при желании) и сохранять тип (с ограничением, для которого требуется полноценный стандартный конструктор по умолчанию):

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

} 
161

Я хотел бы сделать это следующим образом:

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

Простой и легкий.Согласно this blog post, он даже быстрее, чем большинство циклов, поскольку его базовая реализация обращается к элементам по индексу, а не счету (see this answer).

Это будет, конечно, исключение, если есть дубликаты, поэтому вам нужно будет проверить перед слиянием.

3

На основании приведенных выше ответов, но добавление Func-параметр, чтобы абонент обрабатывать дублированные:

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

партия в значительной степени мертв, но вот «улучшенная» версия 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; 
} 
1

Слияние с использованием метода расширения. Он не генерирует исключение, если есть дубликаты ключей, но заменяет эти ключи клавишами из второго словаря.

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

Слияние с использованием EqualityComparer, который отображает элементы для сравнения с другим значением/типа. Здесь мы будем отображать от KeyValuePair (тип элемента при перечислении словаря) до 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(); 
    } 
} 

Использование:

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

Я очень поздно к партии и, возможно, что-то отсутствует, но если либо нет дубликатов ключей или, как говорит О. П., «В случае столкновения, не имеет значения, какое значение сохраняется в dict, если оно последовательное, «что не так с этим (слияние D2 в D1)?

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

Это кажется простым, может быть, слишком простым, интересно, не хватает ли я чего-то. Это то, что я использую в некотором коде, где я знаю, что нет дубликатов ключей. Тем не менее, я все еще в тестировании, поэтому мне бы хотелось узнать сейчас, если я что-то пропустил, вместо того, чтобы узнать позже.

13

Следующие работы для меня. Если есть дубликаты, он будет использовать значение 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]); 
} 
5

Учитывая performance of dictionary key lookups and deletes, так как они являются хэш-операции, и принимая во внимание формулировку вопроса был лучший путь, я думаю, что ниже вполне обоснованный подход, а остальные немного чрезмерно сложным, ИМХО ,

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.

2

@Tim: Должен быть комментарий, но комментарии не позволяют редактировать код.

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

Примечание: Я применил изменение, @ANeves к решению по Orsich @ Андрей, так что MergeLeft будет выглядеть так:

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

Я знаю, что это старый вопрос, но так как мы теперь LINQ вы можете сделать это в одной строке, как это

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

или

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

или:

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

результат является объединением, где для повторяющихся записей «у» выигрывает.