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


493

Какой лучший способ объединить 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, если оно согласовано.


175
Не имеет значения, но для тех, кто хочет объединить только два словаря без проверки дубликатов ключей, это прекрасно работает:dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
Benjol 14.12.11

6
@Benjol, вы могли бы добавить это в разделе ответов
M.Kumaran

4
Слияние Clojure в C #:dict1.Concat(dict2).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value)
Брюс

@Benjol: устраните дубликаты, предпочитая записи из первого словаря с dictA.Concat(dictB.Where(kvp => !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=> kvp.Key, kvp => kvp.Value).
Suncat2000

Ответы:


321

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

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


3
Чтобы фактически объединить значения вместо того, чтобы просто брать их из первого словаря, вы можете заменить group => group.First () на group => group.SelectMany (value => value) в отредактированном ответе Джона Скита.
andreialecu

3
Теперь мне интересно, что GroupBy подойдет лучше, чем ToLookup? Я думаю, что ILookup просто добавляет индексатор, свойство размера и содержит метод поверх IGrouping - так что это должно быть немного быстрее?
Toong

2
@toong: Либо должно быть хорошо, если честно. Использование GroupByдействительно могло бы быть немного более эффективным - но я сомневаюсь, что это было бы важно в любом случае.
Джон Скит

1
@Joe: Это достаточно просто, предоставив компаратор для ToDictionaryвызова метода. Я предпочел бы, чтобы ответ был более простым для более распространенного случая, и я надеюсь, что любой, кому нужен пользовательский компаратор, способен найти соответствующую перегрузку.
Джон Скит

1
@Serge: Я предлагаю вам задать новый вопрос с точными требованиями.
Джон Скит

265

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

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

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

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


169
Если дубликаты имеют значение, используйте dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value). Таким образом, значения из dictionaryFromпереопределяют значение для возможно существующего ключа.
okrumnow

7
Это вряд ли будет быстрее, чем цикл - вы забываете, что ToList () фактически копирует словарь. Быстрее, хотя код! ;-)
Сорен Бойсен

6
Это быстрее .. Так как ToList () фактически не копирует словарь, он просто копирует ссылки на элементы внутри них. По сути, сам объект списка будет иметь новый адрес в памяти, объекты такие же!
GuruC

4
Сообщение в блоге пропало, но уйма машина обратного хода: web.archive.org/web/20150311191313/http://diditwith.net/2006/10/…
CAD bloke

1
Хм, лучше , чем в ответ Джон тарелочкам, я полагаю.
Снаđошƒаӽ

102

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

}

Отлично сработано. Это было именно то, что мне было нужно. Хорошее использование дженериков. Договорились, что синтаксис немного неловкий, но с этим ничего не поделаешь.
Питер М

2
Мне понравилось это решение, но есть предостережение: если this T meсловарь был объявлен с использованием new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase)или чего-то подобного, результирующий словарь не будет сохранять то же поведение.
Жоао Портела

3
Если вы сделаете mea Dictionary<K,V>, то сможете сделать var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);, и вы также избежите дополнительного Tпараметра type.
Ноябрь

2
У кого-нибудь есть пример использования этого зверя?
Тим

2
@Tim: Я добавил пример для зверя ниже, я добавил решение от ANeves, и в результате получился более одомашненный зверь :)
keni

50

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

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

22

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

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

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

1
Просто интересно - разве Союз не будет работать? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages()))Используя его в рабочем коде, если есть какие-либо недостатки, пожалуйста, дайте мне знать! :)
Марс Робертсон

1
@Michal: Union вернет значение, в IEnumerable<KeyValuePair<TKey, TValue>>котором равенство определяется равенством ключа и значения (существует перегрузка для учета альтернатив). Это хорошо для цикла foreach, где это все, что необходимо, но в некоторых случаях вам действительно нужен словарь.
Тимоти

15

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

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

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


4
Одно из самых чистых и читаемых решений imo, а также избегает ToList (), который используют многие другие.
404

15

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

@EsbenSkovPedersen Я не думаю, что столкнулся с этим, когда я ответил на это (новый C # dev)
Ethan Reesor

Мне пришлось удалить «где TValue: класс», чтобы использовать Guid в качестве значения
om471987

@ om471987 Да, Guid - это структура, а не класс, так что это имеет смысл. Я думаю, что изначально у меня были некоторые проблемы, когда я не добавил этот пункт. Больше не помню почему.
Итан Реесор

10

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

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

Здесь перечислено много отличных решений, но мне больше нравится простота этого решения. Просто мои личные предпочтения.
Джейсон

3
if(first == null)тогда эта логика бесполезна, потому что firstнет refи не возвращается. И это не может быть, refпотому что он объявлен как экземпляр интерфейса; Вам нужно либо удалить проверку ошибок, либо объявить ее как экземпляр класса, либо просто вернуть новый словарь (без метода расширения).
Грауль

@Jesdisciple - удалил проблему согласно вашему предложению
Эндрю Гарри

8

Вариант 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());

6

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

4
Он вроде добавляет к другим ответам здесь, отметив, что вы можете сделать класс DictionaryExtensions еще лучше. Возможно, этот вопрос должен стать вики или классом, зарегистрированным для git ...
Крис Москини

5

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

    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. заявление.


4

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

4

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

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

Близко к тому, что я написал сам. Это, конечно, будет работать только для Dictionaryтипов. Могут быть добавлены перегрузки для других типов словаря ( ConcurrentDictionaryи ReadOnlyDictionaryт. Д.). Я не думаю, что создание нового списка необходимо, и concat необходим лично. Сначала я итерировал массив параметров, затем итерировал каждый KVP каждого словаря. Я не вижу отступления.
раздавить

Вы можете использовать IDictionary вместо словаря
Mariusz Jamro

Вы всегда будете получать Dictionaryобъект, даже если вы используете метод расширения для ConcurrentDictionary. Это может привести к трудно прослеживаемым ошибкам.
Марсель

2

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

Извините за downvote @Cruces, но ваш первый пример дублирует ответ stackoverflow.com/a/6695211/704808, а ваш второй пример просто отбрасывает развернутый IEnumerable <KeyValuePair <string, string >> после добавления каждого элемента из mergee.
Вейр

2

Страшно видеть сложные ответы, будучи новичком в 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
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, чтобы принудительно применить выбор для звонящего и сделать разработчика осведомленным о том, какие результаты будут!


Уточненный ответ ниже без ненужного двоичного перечисления, когда будет делать bool ...
mattjs

2

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

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

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

1

Упрощенный от использования по сравнению с моим более ранним ответом с использованием 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;
        });
    }
}

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

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

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


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

0

Версия от @ user166390 ответа с добавленным 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;
    }

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.