Слияние словарей в C#
каков наилучший способ объединить 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 и Джон Скит, но я думал о чем-то, что обрабатывает дубликаты ключей. В случае столкновения не имеет значения, какое значение сохраняется в dict, если оно согласовано.
20 ответов:
Это частично зависит от того, что вы хотите произойти, если вы столкнетесь с дубликатами. Например, вы могли бы сделать:
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());Это немного некрасиво и неэффективно - но это самый быстрый способ сделать это с точки зрения кода. (Я не проверял его, по общему признанию.)
Я бы сделал так:
dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));просто и легко. Согласно этот блог это даже быстрее, чем большинство циклов, поскольку его базовая реализация обращается к элементам по индексу, а не перечислителю (см. Этот ответ).
это, конечно, вызовет исключение, если есть дубликаты, поэтому вам придется проверить перед слиянием.
Ну, я опаздываю на вечеринку, но вот что я использую. Он не взрывается, если есть несколько ключей (ключи" righter "заменяют ключи "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; } }
тривиальное решение:
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; }
Dictionary<String, String> allTables = new Dictionary<String, String>(); allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
попробуйте следующее
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); }
следующее работает для меня. Если есть дубликаты, он будет использовать значение 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]); }
вот вспомогательная функция, которую я использую:
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); } } }
как насчет добавления
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); }
Я очень опаздываю на вечеринку и, возможно, что-то упускаю, но если либо нет дубликатов ключей, либо, как говорит OP, "в случае столкновения не имеет значения, какое значение сохраняется в dict, пока оно согласовано", что не так с этим (слияние D2 в D1)?
foreach (KeyValuePair<string,int> item in D2) { D1[item.Key] = item.Value; }это кажется достаточно простым, может быть, слишком просто, мне интересно, если я что-то упускаю. Это то, что я использую в каком-то коде, где я знаю, что нет дубликатов ключей. Я все еще в тестировании, хотя, поэтому я хотел бы знать сейчас, если я что-то упускаю из виду, вместо того, чтобы узнать позже.
учитывая производительность поиска и удаления ключей словаря поскольку они являются хэш-операциями, и учитывая формулировку вопроса было лучшие кстати, я думаю, что ниже приведен совершенно правильный подход, а остальные немного усложнены, ИМХО.
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.
на основе ответов выше, но добавление 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; }
@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; }
Я знаю, что это старый вопрос, но так как у нас теперь есть 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));
слияние с использованием метода расширения. Он не выбрасывает исключение, когда есть дубликаты ключей, но заменяет эти ключи ключами из второго словаря.
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);
или :
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 для обеспечения выбора для вызывающего абонента и сделать разработчик осведомлен о том, что результаты будут!
Comments