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

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

433 голосов
/ 16 ноября 2008

Как лучше всего объединить 2 или более словарей (Dictionary<T1,T2>) в C #? (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);

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

Ответы [ 23 ]

5 голосов
/ 13 октября 2014

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

    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.

3 голосов
/ 12 апреля 2013

Партия уже почти мертва, но вот "улучшенная" версия 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;
}
3 голосов
/ 20 февраля 2013

Основано на ответах выше, но добавлен 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));
}
2 голосов
/ 26 января 2017

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

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

Вот несколько простых ответов.
Объединение словарей 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
}

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

2 голосов
/ 22 марта 2015

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

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;
    }
2 голосов
/ 08 мая 2019

Вариант 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());
1 голос
/ 08 ноября 2013

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

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);
1 голос
/ 12 сентября 2018
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 для обеспечения выбора для вызывающей стороны и информирования разработчика о том, какими будут результаты!

0 голосов
/ 12 апреля 2019
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;
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...