Об’єднання словників у 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);

EDIT: Отримав круте рішення від JaredPar та Jon Skeet, але я думав про те, що обробляє дублюючі ключі. У разі зіткнення не має значення, яке значення зберігається в дикті, доки воно є послідовним.


174
Неспоріднене, але для тих, хто хоче об’єднати лише два словники без dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
повторних

6
@Benjol ви могли додати це у розділі відповідей
М.Кумаран

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

Відповіді:


320

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

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 та розуміння, що словник підтримує ітерацію своїх пар ключів / значень.


3
Щоб насправді об'єднати значення замість того, щоб просто взяти їх із першого словника, ви можете замінити group => group.First () на group => group.SelectMany (value => value) у відредагованій відповіді Джона Скета.
andreialecu

3
Тепер мені цікаво, що GroupBy краще підходить, ніж ToLookup? Я думаю, що ILookup просто додає індексатор, властивість розміру і містить метод на вершині IGrouping - тож він повинен бути крихітним трішки швидше?
Toong

2
@toong: Будь добре, якщо чесно. Використання GroupByсправді може бути дещо ефективнішим - але я сумніваюся, що це було б значущим.
Джон Скіт

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

1
@Serge: Я пропоную вам задати нове запитання з точними вимогами.
Джон Скіт

264

Я б це зробив так:

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

Просто і легко. Відповідно до цього повідомлення в блозі, це навіть швидше, ніж у більшості циклів, оскільки його основна реалізація отримує доступ до елементів за індексом, а не за перерахунком (див. Цю відповідь) .

Звичайно, це буде винятком, якщо є дублікати, тому вам доведеться перевірити перед об'єднанням.


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

7
Це навряд чи буде швидше, ніж цикл - ви забудете, що ToList () фактично закінчує копіювання словника. Швидше кодувати, хоча! ;-)
Søren Boisen

6
Це швидше. Оскільки ToList () фактично не закінчує копіювання словника, він просто копіює посилання елементів всередині них. В основному сам об'єкт списку матиме нову адресу в пам'яті, об'єкти такі самі !!
GuruC


1
Хм, краще, ніж відповідь Джона Скіта.
Snađошƒаӽ

102

Це не вибухає, якщо є кілька клавіш (клавіші "більш" замінюють клавіші "ліфтер"), можуть об'єднати ряд словників (за бажанням) і зберегти тип (з обмеженням, що він потребує значущого за замовчуванням публічного конструктора):

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
Якщо ви зробите meце Dictionary<K,V>, ви можете зробити це var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);, і ви також уникаєте зайвихT типу.
ANeves

1
Хтось має приклад, як користуватися цим звіром?
Тім

1
@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

Я дуже запізнююся на партію і, можливо, щось пропускаю, але якщо або немає дублікатів ключів, або, як каже ОП, "У разі зіткнення не має значення, яке значення зберігається в дикті, якщо це послідовно, "що не так з цим (об'єднання 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)
Етан Резор

Мені довелося видалити "де TValue: class", щоб використовувати 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: Якщо у вас може бути дублікат ключа, вам доведеться обробляти дублікат ключа, використовуючи пункт де.

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 ще приємнішим. Можливо, це питання повинно стати вікі або класом, який зареєстрований, щоб зібрати ....
Кріс Москіні

5

Враховуючи ефективність пошуку ключових словників та видалень, оскільки вони є хеш-операціями, і вважаючи, що формулювання питання є найкращим способом, я вважаю, що нижче ідеально правильний підхід, а інші трохи складніші, IMHO.

    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

Вечірка зараз майже мертва, але ось "покращена" версія користувача166390, яка потрапила в мою бібліотеку розширень. Окрім деяких деталей, я додав делегата для обчислення об'єднаного значення.

/// <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 кожного словника. Я не бачу нічиї назад.
розчавити

Ви можете використовувати ID Dictionary замість словника
Mariusz Jamro

Ви завжди отримаєте Dictionaryоб'єкт, навіть якщо б ви використовували метод розширення на a 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.
weir

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



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

Спрощений у використанні порівняно з моєю попередньою відповіддю із значенням за замовчуванням неруйнівного злиття, якщо існує, або перезаписати цілком, якщо істинно, а не з використанням перерахунків. Він все ще відповідає моїм власним потребам, без будь-якого фантазійного коду ніколи не потрібно:

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.