Слияние словарей в C #

Как лучше всего объединить 2 или более словарей (Dictionary) в C #? (3. 0 функций, таких как LINQ в порядке).

Я думаю о подписи метода в соответствии с:

public static Dictionary
                 Merge(Dictionary[] dictionaries);

или

public static Dictionary
                 Merge(IEnumerable> dictionaries);

РЕДАКТИРОВАТЬ: Получил классное решение от JaredPar и Jon Skeet, но я думал о чем-то, что обрабатывает дубликаты ключей. В случае коллизии не имеет значения, какое значение сохранено в dict, если оно согласовано.

вопрос задан 16.11.2008
orip
45654 репутация

20 ответов


  • 245 рейтинг

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

    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 и понимание того, что словарь поддерживает итерацию по его парам ключ / значение.

    ответ дан Jon Skeet, с репутацией 1057123, 16.11.2008
  • 191 рейтинг

    Я бы сделал это так:

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

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

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

    ответ дан Jonas Stensved, с репутацией 7468, 14.07.2011
  • 89 рейтинг

    Ну, я опаздываю на вечеринку, но вот что я использую. Он не взрывается при наличии нескольких ключей («правильные» ключи заменяют «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(this T me, params IDictionary[] others)
            where T : IDictionary, new()
        {
            T newMap = new T();
            foreach (IDictionary src in
                (new List> { me }).Concat(others)) {
                // ^-- echk. Not quite there type-system.
                foreach (KeyValuePair p in src) {
                    newMap[p.Key] = p.Value;
                }
            }
            return newMap;
        }
    
    }
    
    ответ дан David Osborn, с репутацией 1821, 21.04.2010
  • 41 рейтинг

    Тривиальное решение будет:

    using System.Collections.Generic;
    ...
    public static Dictionary
        Merge(IEnumerable> dictionaries)
    {
        var result = new Dictionary();
        foreach (var dict in dictionaries)
            foreach (var x in dict)
                result[x.Key] = x.Value;
        return result;
    }
    
    ответ дан orip, с репутацией 45654, 16.11.2008
  • 17 рейтинг
    Dictionary allTables = new Dictionary();
    allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
    
    ответ дан David Osborn, с репутацией 1821, 6.04.2010
  • 17 рейтинг

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

    static Dictionary
        Merge(this IEnumerable> enumerable)
    {
        return enumerable.SelectMany(x => x).ToDictionary(x => x.Key, y => y.Value);
    }
    
    ответ дан JaredPar, с репутацией 555649, 16.11.2008
  • 13 рейтинг

    У меня работает следующее. Если есть дубликаты, он будет использовать значение dictA.

    public static IDictionary Merge(this IDictionary dictA, IDictionary dictB)
        where TValue : class
    {
        return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);
    }
    
    ответ дан Ethan Reesor, с репутацией 1243, 8.08.2014
  • 8 рейтинг

    Вот вспомогательная функция, которую я использую:

    using System.Collections.Generic;
    namespace HelperMethods
    {
        public static class MergeDictionaries
        {
            public static void Merge(this IDictionary first, IDictionary second)
            {
                if (second == null || first == null) return;
                foreach (var item in second) 
                    if (!first.ContainsKey(item.Key)) 
                        first.Add(item.Key, item.Value);
            }
        }
    }
    
    ответ дан Andrew Harry, с репутацией 7070, 6.08.2009
  • 6 рейтинг

    Как насчет добавления перегрузки params?

    Кроме того, вы должны ввести их как IDictionary для максимальной гибкости.

    public static IDictionary Merge(IEnumerable> dictionaries)
    {
        // ...
    }
    
    public static IDictionary Merge(params IDictionary[] dictionaries)
    {
        return Merge((IEnumerable) dictionaries);
    }
    
    ответ дан Bryan Watts, с репутацией 34453, 16.11.2008
  • 6 рейтинг

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

    foreach (KeyValuePair item in D2)
                {
                     D1[item.Key] = item.Value;
                }
    

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

    ответ дан codingatty, с репутацией 859, 8.07.2014
  • 5 рейтинг

    Учитывая производительность поиска по ключевым словам в словаре и удаления , поскольку они являются операциями хэширования, и учитывая, что формулировка вопроса была наилучшим способом , я думаю, что приведенный ниже является совершенно корректным подходом, а другие немного сложнее , ПО МОЕМУ МНЕНИЮ.

        public static void MergeOverwrite(this IDictionary dictionary, IDictionary 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(this ConcurrentDictionary dictionary, IDictionary 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 MergeAllOverwrite(IList> allDictionaries)
        {
            var initSize = allDictionaries.Sum(d => d.Count);
            var resultDictionary = new Dictionary(initSize);
            allDictionaries.ForEach(resultDictionary.MergeOverwrite);
            return resultDictionary;
        }
    

    Обратите внимание, что я взял IList для этого метода. , , в основном потому, что если вы берете IEnumerable, вы открыли для себя несколько перечислений одного и того же набора, что может быть очень дорогостоящим, если вы получили свою коллекцию словарей из отложенного оператора LINQ.

    ответ дан GoldPaintedLemons, с репутацией 381, 13.10.2014
  • 3 рейтинг

    Основано на ответах выше, но добавлен Func-параметр, позволяющий вызывающей стороне обрабатывать дубликаты:

    public static Dictionary Merge(this IEnumerable> dicts, 
                                                               Func, TValue> resolveDuplicates)
    {
        if (resolveDuplicates == null)
            resolveDuplicates = new Func, TValue>(group => group.First());
    
        return dicts.SelectMany, KeyValuePair>(dict => dict)
                    .ToLookup(pair => pair.Key, pair => pair.Value)
                    .ToDictionary(group => group.Key, group => resolveDuplicates(group));
    }
    
    ответ дан toong, с репутацией 1064, 20.02.2013
  • 3 рейтинг

    Партия уже почти мертва, но вот «улучшенная» версия user166390, попавшая в мою библиотеку расширений Помимо некоторых деталей, я добавил делегата для вычисления объединенного значения.

    /// 
    /// Merges a dictionary against an array of other dictionaries. ///
     
    /// The type of the resulting dictionary.
    /// The type of the key in the resulting dictionary.
    /// The type of the value in the resulting dictionary.
    /// 
    The source dictionary.
    /// 
    A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)
    /// 
    Dictionaries to merge against.
    /// The merged dictionary.
    public static TResult MergeLeft(
        this TResult source,
        Func mergeBehavior,
        params IDictionary[] mergers)
        where TResult : IDictionary, new()
    {
        var result = new TResult();
        var sources = new List> { 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;
    }
    
    ответ дан gxtaillon, с репутацией 623, 11.04.2013
  • 2 рейтинг

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

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

    Примечание. Я применил модификацию @ANeves к решению @Andrew Orsich, поэтому MergeLeft теперь выглядит так:

    public static Dictionary MergeLeft(this Dictionary me, params IDictionary[] others)
        {
            var newMap = new Dictionary(me, me.Comparer);
            foreach (IDictionary src in
                (new List> { me }).Concat(others))
            {
                // ^-- echk. Not quite there type-system.
                foreach (KeyValuePair p in src)
                {
                    newMap[p.Key] = p.Value;
                }
            }
            return newMap;
        }
    
    ответ дан keni, с репутацией 1180, 22.03.2015
  • 2 рейтинг

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

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

    или

    mergee.ToList().ForEach(kvp => merged.Append(kvp));
    
    ответ дан Cruces, с репутацией 561, 26.01.2017
  • 1 рейтинг

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

    internal static class DictionaryExtensions
    {
        public static Dictionary Merge(this Dictionary first, Dictionary second)
        {
            if (first == null) throw new ArgumentNullException("first");
            if (second == null) throw new ArgumentNullException("second");
    
            var merged = new Dictionary();
            first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
            second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
    
            return merged;
        }
    }
    

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

    Dictionary merged = first.Merge(second);
    
    ответ дан Andrew Mikhailov, с репутацией 644, 8.11.2013
  • 0 рейтинг
    using System.Collections.Generic;
    using System.Linq;
    
    public static class DictionaryExtensions
    {
        public enum MergeKind { SkipDuplicates, OverwriteDuplicates }
    
        public static void Merge(this IDictionary target, IDictionary source, MergeKind kind = MergeKind.SkipDuplicates)
        {
            source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });
        }
    }
    
    ответ дан mattjs, с репутацией 1, 12.09.2018
  • 0 рейтинг

    Слияние с использованием EqualityComparer, который сопоставляет элементы для сравнения с другим значением / типом. Здесь мы отобразим от KeyValuePair (тип элемента при перечислении словаря) до Key.

    public class MappedEqualityComparer : EqualityComparer
    {
        Func _map;
    
        public MappedEqualityComparer(Func map)
        {
            _map = map;
        }
    
        public override bool Equals(T x, T y)
        {
            return EqualityComparer.Default.Equals(_map(x), _map(y));
        }
    
        public override int GetHashCode(T obj)
        {
            return _map(obj).GetHashCode();
        }
    }
    

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

    // if dictA and dictB are of type Dictionary
    var dict = dictA.Concat(dictB)
                    .Distinct(new MappedEqualityComparer,int>(item => item.Key))
                    .ToDictionary(item => item.Key, item=> item.Value);
    
    ответ дан BSharp, с репутацией 411, 29.05.2014
  • 0 рейтинг

    или:

    public static IDictionary Merge( IDictionary x, IDictionary 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);
        }
    

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

    ответ дан jtroconisa, с репутацией 91, 11.10.2017
  • 0 рейтинг

    Испугался, увидев сложные ответы, будучи новичком в C #.

    Вот несколько простых ответов.
    Слияние d1, d2 и так далее. , словари и обрабатывают любые перекрывающиеся ключи («b» в примерах ниже):

    Пример 1

    {
        // 2 dictionaries,  "b" key is common with different values
    
        var d1 = new Dictionary() { { "a", 10 }, { "b", 21 } };
        var d2 = new Dictionary() { { "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
    }
    

    Пример 2

    {
        // 3 dictionaries,  "b" key is common with different values
    
        var d1 = new Dictionary() { { "a", 10 }, { "b", 21 } };
        var d2 = new Dictionary() { { "c", 30 }, { "b", 22 } };
        var d3 = new Dictionary() { { "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
    }
    

    Для более сложных сценариев см. Другие ответы.
    Надеюсь, что это помогло.

    ответ дан Manohar Reddy Poreddy, с репутацией 3976, 5.09.2018