cref - summary tag in c#
Fusionando diccionarios en C# (20)
¿Cuál es la mejor manera de combinar 2 o más diccionarios ( Dictionary<T1,T2>
) en C #? (3.0 características como LINQ están bien).
Estoy pensando en una firma de método a lo largo de las líneas de:
public static Dictionary<TKey,TValue>
Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);
o
public static Dictionary<TKey,TValue>
Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);
EDITAR: Obtuve una solución genial de JaredPar y Jon Skeet, pero estaba pensando en algo que maneja claves duplicadas. En caso de colisión, no importa qué valor se guarda en el dict, siempre que sea consistente.
¿Qué hay de agregar una sobrecarga de params
?
Además, debe escribirlos como IDictionary
para la máxima flexibilidad.
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);
}
@Tim: debe ser un comentario, pero los comentarios no permiten la edición del código.
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);
Nota: apliqué la modificación de @Aeves a la solución de @Andrew Orsich, por lo que MergeLeft tiene este aspecto ahora:
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;
}
Aquí hay una función de ayuda que uso:
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);
}
}
}
Basado en las respuestas anteriores, pero agregando un parámetro Func para permitir que la persona que llama maneje los duplicados:
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));
}
Bueno, llego tarde a la fiesta, pero esto es lo que uso. No explota si hay varias claves (las claves "más rectas" reemplazan las claves "más a la izquierda"), puede combinar una serie de diccionarios (si se desea) y conserva el tipo (con la restricción de que requiere un constructor público predeterminado significativo):
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;
}
}
Esto depende en parte de lo que quieras que suceda si te topas con duplicados. Por ejemplo, podrías hacer:
var result = dictionaries.SelectMany(dict => dict)
.ToDictionary(pair => pair.Key, pair => pair.Value);
Eso explotará si obtienes alguna clave duplicada.
EDITAR: Si usa ToLookup, obtendrá una búsqueda que puede tener múltiples valores por clave. Entonces podrías convertir eso en un diccionario:
var result = dictionaries.SelectMany(dict => dict)
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key, group => group.First());
Es un poco feo, e ineficiente, pero es la forma más rápida de hacerlo en términos de código. (No lo he probado, es cierto.)
Por supuesto, puede escribir su propio método de extensión ToDictionary2 (con un nombre mejor, pero no tengo tiempo para pensar en uno ahora): no es muy difícil de hacer, solo sobrescribir (o ignorar) las claves duplicadas. Lo importante (en mi opinión) es utilizar SelectMany y darse cuenta de que un diccionario admite iteración sobre sus pares clave / valor.
Fusión mediante un método de extensión. No lanza una excepción cuando hay claves duplicadas, pero reemplaza esas claves con claves del segundo diccionario.
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;
}
}
Uso:
Dictionary<string, string> merged = first.Merge(second);
Fusionar usando un EqualityComparer
que mapea elementos para compararlos con un valor / tipo diferente. Aquí KeyValuePair
desde KeyValuePair
(tipo de elemento al enumerar un diccionario) a 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();
}
}
Uso:
// 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);
Intenta lo siguiente
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);
}
La fiesta está bastante muerta ahora, pero aquí hay una versión "mejorada" del usuario 166390 que se abrió camino en mi biblioteca de extensiones. Aparte de algunos detalles, agregué un delegado para calcular el valor combinado.
/// <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;
}
La solución trivial sería:
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;
}
Llego tarde a la fiesta y quizás falte algo, pero si no hay claves duplicadas o, como dice el OP, "en caso de colisión, no importa qué valor se guarda en el dict, siempre que sea consistente, "¿qué hay de malo en esto (fusionar D2 en D1)?
foreach (KeyValuePair<string,int> item in D2)
{
D1[item.Key] = item.Value;
}
Parece bastante simple, quizás demasiado simple, me pregunto si me estoy perdiendo algo. Esto es lo que estoy usando en un código donde sé que no hay claves duplicadas. Sin embargo, todavía estoy en pruebas, así que me encantaría saber ahora si estoy pasando por alto algo, en lugar de descubrirlo más tarde.
Lo haría así:
dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));
Simple y fácil. Según esta publicación del blog , es incluso más rápido que la mayoría de los bucles, ya que su implementación subyacente accede a los elementos mediante un índice en lugar de un enumerador (consulte esta respuesta) .
Por supuesto, lanzará una excepción si hay duplicados, por lo que tendrá que verificar antes de fusionar.
Lo siguiente funciona para mí. Si hay duplicados, usará el valor de 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]);
}
Me asusté al ver respuestas complejas, siendo nuevo en C #.
Aquí hay algunas respuestas simples.
Fusionando los diccionarios d1, d2, etc., y maneje las teclas superpuestas ("b" en los ejemplos a continuación):
Ejemplo 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
}
Ejemplo 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
}
Para escenarios más complejos, ver otras respuestas.
Espero que haya ayudado.
Sé que esta es una pregunta antigua, pero como ahora tenemos LINQ, puede hacerlo en una sola línea como esta
Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));
o
mergee.ToList().ForEach(kvp => merged.Append(kvp));
Teniendo en cuenta el rendimiento de las búsquedas y eliminaciones de claves del diccionario, ya que son operaciones hash, y considerando que la redacción de la pregunta fue la mejor manera, creo que a continuación es un enfoque perfectamente válido, y los otros son un poco demasiado complicados, 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);
}
}
O bien, si estás trabajando en una aplicación multiproceso y tu diccionario debe estar seguro de subprocesos de todos modos, deberías estar haciendo esto:
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);
}
}
A continuación, puede envolver esto para que se maneje una enumeración de diccionarios. En cualquier caso, está mirando sobre ~ O (3n) (todas las condiciones son perfectas), ya que .Add()
hará una .Add()
adicional, innecesaria pero prácticamente gratuita, Contains()
detrás de escena. No creo que se ponga mucho mejor.
Si desea limitar las operaciones adicionales en colecciones grandes, debe resumir el Count
de cada diccionario que está a punto de fusionar y configurar la capacidad del diccionario de destino, lo que evita el costo posterior del cambio de tamaño. Entonces, el producto final es algo como esto ...
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;
}
Tenga en cuenta que IList<T>
una IList<T>
para este método ... principalmente porque si IEnumerable<T>
una IEnumerable<T>
, se ha abierto a múltiples enumeraciones del mismo conjunto, lo que puede ser muy costoso si obtiene su colección de diccionarios de una declaración LINQ diferida.
o:
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);
}
el resultado es una unión donde para las entradas duplicadas "y" gana.
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
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; });
}
Puede omitir / ignorar (predeterminado) o sobrescribir los duplicados: y Bob es su tío siempre y cuando no sea muy quisquilloso con el rendimiento de Linq, sino que prefiera un código mantenible conciso como lo hago yo: en cuyo caso, puede eliminar el MergeKind predeterminado. ¡una opción para la persona que llama y que el desarrollador conozca cuáles serán los resultados!