Как лучше всего объединить 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, если оно согласовано.
@Benjol, вы могли бы добавить это в разделе ответов
Слияние 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).





Тривиальное решение было бы таким:
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;
}
Спасибо. Получил все, что хочу.
Попробуйте следующее
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);
}
Это частично зависит от того, что вы хотите, если вы столкнетесь с дубликатами. Например, вы можете:
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 и понимание того, что словарь поддерживает итерацию по парам ключ / значение.
Чтобы фактически объединить значения вместо того, чтобы просто брать их из первого словаря, вы можете заменить group => group.First () на group => group.SelectMany (value => value) в отредактированном ответе Джона Скита.
Теперь мне интересно, что GroupBy подходит лучше, чем ToLookup? Я думаю, что ILookup просто добавляет индексатор, свойство размера и содержит метод поверх IGrouping - так что это должно быть немного быстрее?
@toong: Честно говоря, все должно быть в порядке. Использование GroupBy действительно могло бы быть немного более эффективным, но я сомневаюсь, что это будет иметь значение в любом случае.
Незначительная деталь: наряду с большинством других ответов это не касается ситуации, когда (некоторые из) входных словарей используют компаратор не по умолчанию: например, ключ строки без учета регистра. Полностью общее решение должно позволить вызывающей стороне указать средство сравнения для целевого словаря. Или скопируйте в существующий словарь, как ответ Йонаса Стенсведа.
@Joe: Это достаточно просто, предоставив средство сравнения для вызова метода ToDictionary. Я бы предпочел, чтобы ответ был проще для более распространенного случая, и я надеюсь, что любой, кому нужен пользовательский компаратор, сможет найти соответствующую перегрузку.
@Serge: Предлагаю вам задать новый вопрос с точными требованиями.
Серж - решение Джона работает для набора словарей, содержащихся в переменных .. словарях. Итак, для двух словарей у вас будет список из двух словарей. SelectMany объединит их в список пар ключ / значение, которые затем используются для создания нового словаря.
Просто обратите внимание, что если вы вместо этого используете .Last, он будет «обновлять» исходные значения вместо блокировки и никогда не изменяться.
Как насчет добавления перегрузки 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);
}
Он как бы дополняет другие ответы здесь, отмечая, что вы можете сделать класс DictionaryExtensions еще лучше. Возможно, этот вопрос должен стать вики-страницей или классом, зарегистрированным в git ....
Вот вспомогательная функция, которую я использую:
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);
}
}
}
Здесь перечислено множество отличных решений, но мне больше всего нравится простота этого. Просто мое личное предпочтение.
if (first == null), тогда эта логика бесполезна, потому что first не является ref и не возвращается. И это не может быть ref, потому что он объявлен как экземпляр интерфейса; вам нужно либо удалить проверку ошибок, либо объявить ее как экземпляр класса, либо просто вернуть новый словарь (без метода расширения).
@Jesdisciple - удалил проблему согласно вашему предложению
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
Просто интересно - не будет ли просто Союз просто работать? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages())) Если вы используете его в производственном коде, дайте мне знать, если есть какие-либо недостатки! :)
@Michal: Union вернет IEnumerable<KeyValuePair<TKey, TValue>>, где равенство определяется равенством ключа и значения (есть перегрузка, позволяющая использовать альтернативы). Это нормально для цикла foreach, где это все, что необходимо, но есть случаи, когда вам действительно нужен словарь.
Это не сработает, если есть несколько ключей («правые» клавиши заменяют «лефтерные» ключи), может объединить несколько словарей (при желании) и сохранить тип (с ограничением, что для этого требуется значимый общедоступный конструктор по умолчанию):
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;
}
}
Отличная работа. Это было именно то, что мне нужно. Хорошее использование дженериков. Согласны, что синтаксис немного неудобен, но вы ничего не можете с этим поделать.
Мне понравилось это решение, но есть предостережение: если словарь this T me был объявлен с использованием new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase) или чего-то подобного, результирующий словарь не будет сохранять такое же поведение.
Если вы сделаете meDictionary<K,V>, вы можете сделать var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);, и вы также избежите дополнительного параметра типа T.
Есть у кого-нибудь пример, как пользоваться этим зверьком?
@Tim: Я добавил пример для зверя ниже, я добавил решение от ANeves, и в результате получился более прирученный зверь :)
Я бы сделал это так:
dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));
Просто и легко. Согласно это сообщение в блоге, это даже быстрее, чем большинство циклов, поскольку его базовая реализация обращается к элементам по индексу, а не по счетчику (см. этот ответ).
Конечно, если есть дубликаты, это вызовет исключение, поэтому вам нужно будет проверить их перед слиянием.
Если дубликаты имеют значение, используйте dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value). Таким образом, значения из dictionaryFrom переопределяют значение для возможно существующего ключа.
Вряд ли это будет быстрее цикла - вы забываете, что ToList () фактически копирует словарь. Но быстрее кодировать! ;-)
Это быстрее .. Поскольку ToList () фактически не копирует словарь, он просто копирует ссылки на элементы внутри них. В основном сам объект списка будет иметь новый адрес в памяти, объекты такие же !!
Сообщение в блоге исчезло, но ура Wayback machine: web.archive.org/web/20150311191313/http://diditwith.net/2006 / 10 /…
Как насчет выделения памяти? Ссылка не работает, поэтому непонятно, как она может быть лучше, чем foreach, которому не нужно выделять память для нового списка.
Хм, я полагаю, лучше, чем ответ Джона Скита то.
Jon Skeet's answer? Джон СкитНа основе ответов выше, но с добавлением параметра 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));
}
Партия практически мертва, но вот «улучшенная» версия 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;
}
Слияние с использованием метода расширения. Он не генерирует исключение, когда есть повторяющиеся ключи, но заменяет эти ключи ключами из второго словаря.
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);
Слияние с использованием 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);
Я очень опаздываю на вечеринку и, возможно, что-то упускаю, но если либо нет повторяющихся ключей, либо, как говорит OP, «В случае коллизии не имеет значения, какое значение сохраняется в dict, если оно последовательный, "что не так с этим (слияние D2 с D1)?"
foreach (KeyValuePair<string,int> item in D2)
{
D1[item.Key] = item.Value;
}
Это кажется достаточно простым, может быть, слишком простым, интересно, я что-то упускаю. Это то, что я использую в каком-то коде, где я знаю, что нет повторяющихся ключей. Тем не менее, я все еще нахожусь в стадии тестирования, поэтому мне бы хотелось узнать, не упускаю ли я что-то из виду, вместо того, чтобы узнавать позже.
Одно из самых чистых и читаемых решений imo, которое также позволяет избежать ToList (), который используют многие другие.
Следующее работает для меня. Если есть дубликаты, будет использоваться значение 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#)
Мне пришлось удалить 'where TValue: class', чтобы использовать Guid в качестве значения
@ om471987 Да, Guid - это структура, а не класс, так что в этом есть смысл. Думаю, изначально у меня возникла проблема, когда я не добавил этот пункт. Больше не помню почему.
Учитывая производительность поиска и удаления ключей словаря, поскольку они являются хэш-операциями, и учитывая, что формулировка вопроса была Лучший, я думаю, что ниже это совершенно правильный подход, а другие немного сложнее, 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.
@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 необходимо лично. Я просто сначала итерировал массив params, а затем повторил каждый KVP для каждого словаря. Я не вижу отступления.
Вы можете использовать IDictionary вместо Dictionary
Вы всегда будете получать объект Dictionary, даже если вы использовали метод расширения на ConcurrentDictionary. Это может привести к затруднению отслеживания ошибок.
Я знаю, что это старый вопрос, но, поскольку теперь у нас есть 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));
Извините за отрицательный голос @Cruces, но ваш первый пример дублирует ответ stackoverflow.com/a/6695211/704808, а ваш второй пример просто отбрасывает расширенный IEnumerable <KeyValuePair <string, string >> после добавления каждого элемента из mergee.
или же :
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".
Боялся видеть сложные ответы, будучи новичком в 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
}
Для более сложных сценариев см. Другие ответы. Надеюсь, это помогло.
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 ...
Нет ничего выше или ниже, ответы оцениваются на основе их голосов и т. д.
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;
}
Я не думаю, что вам нужен if; просто one[kvp.Key] = two[kvp.Key]; должен охватывать оба случая.
Я согласен с @GuillermoPrandi. Хотя лучше перестраховаться, чем сожалеть
Опция 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());
Версия из ответа @ 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;
}
fromDic.ToList().ForEach(x =>
{
if (toDic.ContainsKey(x.Key))
toDic.Remove(x.Key);
toDic.Add(x);
});
Упрощен с точки зрения использования по сравнению с моим предыдущим ответом со значением по умолчанию неразрушающего слияния, если оно существует, или полностью перезаписано, если оно истинно, а не с использованием перечисления. Он по-прежнему соответствует моим потребностям без необходимости в каком-либо более красивом коде:
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;
});
}
}
Обратите внимание, что если вы используете метод расширения под названием 'Add', вы можете использовать инициализаторы коллекции, чтобы объединить столько словарей, сколько необходимо, например:
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" }
};
Не связано, но для тех, кто хочет объединить только два словаря без проверки дублирующихся ключей, это прекрасно работает:
dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)