Подтвердить что ты не робот

Объединение словарей в С#

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

Я думаю о сигнатуре метода в строках:

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 и Jon Skeet, но я думал о чем-то, что обрабатывает дубликаты ключей. В случае столкновения не имеет значения, какое значение сохраняется в dict, если оно согласовано.

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

Ответ 3

Это не взрывается, если есть несколько ключей ("правильные" ключи заменяют "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;
    }

}

Ответ 4

Тривиальное решение:

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

Ответ 5

Попробуйте выполнить

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

    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.

Ответ 12

Основываясь на ответах выше, но добавляя параметр 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));
}

Ответ 13

Вариант 1. Это зависит от того, что вы хотите сделать, если уверены, что в обоих словарях нет дублирующего ключа. чем вы могли бы сделать:

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

Примечание. Это приведет к ошибке, если в словарях появятся дубликаты ключей.

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

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

Примечание: он не получит дубликат ключа. если будет какой-либо дубликат ключа, он получит ключ dictionary1.

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

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

Ответ 14

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

Ответ 15

@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 к решению @Andrew 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;
    }

Ответ 16

Я знаю, что это старый вопрос, но поскольку теперь у нас есть 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));

Ответ 17

Страшно видеть сложные ответы, будучи новичком в С#.

Вот несколько простых ответов.
Объединение словарей d1, d2 и т.д. И обработка любых перекрывающихся ключей (в следующих примерах "b"):

Пример 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
}

Пример 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
}

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

Ответ 18

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

Вы можете либо пропустить/игнорировать (по умолчанию), либо перезаписать дубликаты: и Боб, ваш дядя, при условии, что вы не слишком суетливы в отношении производительности Linq, но предпочитаете вместо этого лаконичный обслуживаемый код, как я: в этом случае вы можете удалить стандартный MergeKind.SkipDuplicates для принудительного применения выбор для звонящего и сделать разработчика осведомленным о том, какие результаты будут!

Ответ 19

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

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

Ответ 20

Упрощенный от использования по сравнению с моим более ранним ответом с использованием bool по умолчанию неразрушающего слияния, если оно существует, или полностью перезаписать, если оно истинно, вместо использования enum. Он по-прежнему удовлетворяет мои собственные потребности без какого-либо более причудливого кода:

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

Ответ 21

Объединение с помощью 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);

Ответ 22

или:

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

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

Ответ 23

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

Ответ 24

Версия от @user166390 содержит ответ с добавленным параметром IEqualityComparer который позволяет IEqualityComparer регистра.

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

Ответ 25

fromDic.ToList().ForEach(x =>
        {
            if (toDic.ContainsKey(x.Key))
                toDic.Remove(x.Key);
            toDic.Add(x);
        });

Ответ 26

Обратите внимание, что если вы используете метод расширения под названием "Добавить", вы можете использовать инициализаторы коллекций, чтобы объединить столько словарей, сколько необходимо:

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