Fusionar diccionarios en C #

493

¿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 en la línea 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 maneje claves duplicadas. En caso de colisión, no importa qué valor se guarde en el dict mientras sea coherente.

orip
fuente
175
Sin relación, pero para cualquiera que busque fusionar solo dos diccionarios sin verificaciones de claves duplicadas, esto funciona bien:dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
Benjol
66
@Benjol, podrías haber agregado esto en la sección de respuestas
M.Kumaran
44
La fusión de Clojure en C #:dict1.Concat(dict2).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value)
Bruce
@Benjol: elimine los duplicados al preferir las entradas del primer diccionario con dictA.Concat(dictB.Where(kvp => !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=> kvp.Key, kvp => kvp.Value).
Suncat2000

Respuestas:

321

Esto depende en parte de lo que quieras que suceda si te encuentras con duplicados. Por ejemplo, podrías hacer:

var result = dictionaries.SelectMany(dict => dict)
                         .ToDictionary(pair => pair.Key, pair => pair.Value);

Eso arrojará una excepción si obtiene claves duplicadas.

EDITAR: si usa ToLookup, obtendrá una búsqueda que puede tener múltiples valores por clave. Usted podría luego convertir eso a 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, lo admito).

Por supuesto, podría escribir su propio método de extensión ToDictionary2 (con un nombre mejor, pero no tengo tiempo para pensar en uno ahora): no es terriblemente difícil de hacer, simplemente sobrescribiendo (o ignorando) las claves duplicadas. Lo importante (en mi opinión) es usar SelectMany y darse cuenta de que un diccionario admite la iteración sobre sus pares clave / valor.

Jon Skeet
fuente
3
Para fusionar los valores en lugar de simplemente tomarlos del primer diccionario, puede reemplazar group => group.First () con group => group.SelectMany (value => value) en la respuesta editada de Jon Skeet.
andreialecu
3
Ahora me pregunto si GroupBy sería más adecuado que ToLookup. Creo que ILookup solo agrega un indexador, una propiedad de tamaño y contiene un método además de IGrouping, por lo que debe ser un poco más rápido.
toong
2
@toong: Cualquiera de los dos debería estar bien, para ser honesto. De hecho, el uso GroupBypodría ser un poco más eficiente, pero dudo que sea significativo de cualquier manera.
Jon Skeet
1
@ Joe: Eso es bastante simple al proporcionar el comparador a la ToDictionaryllamada al método. Sin embargo, preferiría mantener la respuesta más simple para el caso más común, y espero que cualquiera que necesite un comparador personalizado sea capaz de encontrar la sobrecarga relevante.
Jon Skeet
1
@Serge: le sugiero que haga una nueva pregunta con requisitos precisos.
Jon Skeet el
265

Lo haría así:

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

Simple y fácil. De acuerdo con esta publicación de blog , es aún más rápido que la mayoría de los bucles, ya que su implementación subyacente accede a los elementos por índice en lugar de enumerador (vea esta respuesta) .

Por supuesto, arrojará una excepción si hay duplicados, por lo que deberá verificar antes de fusionar.

Jonas Stensved
fuente
169
Si los duplicados importan, úselos dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value). De esta manera, los valores de dictionaryFromanulan el valor de una clave posiblemente existente.
okrumnow
77
Es poco probable que sea más rápido que un bucle: olvida que ToList () en realidad termina copiando el diccionario. ¡Más rápido para codificar! ;-)
Søren Boisen
66
Esto es más rápido. Dado que ToList () en realidad no termina copiando el diccionario, solo copia las referencias de elementos dentro de ellos. Básicamente, el objeto de la lista tendrá una nueva dirección en la memoria, ¡los objetos son los mismos!
GuruC
44
La publicación del blog desapareció, pero sí máquina Wayback: web.archive.org/web/20150311191313/http://diditwith.net/2006/10/…
CAD bloke
1
Hmm, supongo que mejor que la respuesta de Jon Skeet.
Sнаđошƒаӽ
102

Esto no explota si hay varias claves (las teclas "más correctas" reemplazan a las claves "posteriores"), puede combinar varios diccionarios (si lo 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;
    }

}
Dave
fuente
Bien hecho. Esto era exactamente lo que necesitaba. Buen uso de genéricos. Estuvo de acuerdo en que la sintaxis es un poco incómoda, pero no se puede hacer nada al respecto.
Peter M
2
Me gustó esta solución, pero hay una advertencia: si el this T mediccionario se declaró usando new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase)o algo similar, el diccionario resultante no conservará el mismo comportamiento.
João Portela
3
Si hace meun Dictionary<K,V>, puede hacerlo var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);, y también evita el Tparámetro de tipo adicional .
ANeves
2
¿Alguien tiene un ejemplo de cómo usar esta bestia?
Tim
2
@Tim: agregué un ejemplo para la bestia a continuación, agregué la solución de ANeves y el resultado es una bestia más domesticada :)
keni
50

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;
}
orip
fuente
22

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);
}
JaredPar
fuente
19
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
ctrlalt313373
fuente
1
Solo me pregunto: ¿no solo Union solo funcionaría? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages()))Utilizándolo en el código de producción, si hay inconvenientes, ¡hágamelo saber! :)
Mars Robertson
1
@Michal: Union devolverá un lugar IEnumerable<KeyValuePair<TKey, TValue>>donde la igualdad se define por la igualdad de clave y valor (hay una sobrecarga para permitir alternativas). Esto está bien para un bucle foreach, donde esto es todo lo que es necesario, pero hay casos en los que realmente desea el diccionario.
Timothy
15

Llego muy tarde a la fiesta y tal vez me falte algo, pero si no hay claves duplicadas o, como dice el OP, "En caso de colisión, no importa qué valor se guarde en el dict mientras sea coherente, "¿qué tiene de malo este (fusionar D2 en D1)?

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

Parece bastante simple, tal vez demasiado simple, me pregunto si me estoy perdiendo algo. Esto es lo que estoy usando en algún 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 averiguarlo más tarde.

codificación
fuente
44
Una de las soluciones más limpias y legibles de la OMI y también evita la lista de reproducción () que muchos otros usan.
404
15

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]);
}
Ethan Reesor
fuente
@EsbenSkovPedersen No creo que me haya encontrado con eso cuando respondí esto (nuevo desarrollador de C #)
Ethan Reesor
Tuve que eliminar 'where TValue: class' para usar Guid como valor
om471987
@ om471987 Sí, Guid es una estructura, no una clase, por lo que tiene sentido. Creo que originalmente tuve algún problema cuando no agregué esa cláusula. No recuerdo por qué más.
Ethan Reesor
10

Aquí hay una función auxiliar 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);
        }
    }
}
Andrew Harry
fuente
Aquí se enumeran muchas soluciones excelentes, pero prefiero la simplicidad de esta. Solo mi preferencia personal.
Jason
3
if(first == null)entonces esta lógica es inútil porque firstno refes y no se devuelve. Y no puede ser refporque se declara como una instancia de interfaz; debe eliminar la comprobación de errores o declararla como una instancia de clase o simplemente devolver un nuevo diccionario (sin método de extensión).
Grault
@Jesdisciple - eliminó el problema según su sugerencia
Andrew Harry
8

Opción 1: Esto depende de lo que quiera que suceda si está seguro de que no tiene una clave duplicada en ambos diccionarios. de lo que podrías hacer:

var result = dictionary1.Union(dictionary2).ToDictionary(k => k.Key, v => v.Value)

Nota: Esto arrojará un error si obtiene claves duplicadas en los diccionarios.

Opción 2: si puede tener una clave duplicada, deberá manejar la clave duplicada con el uso de la cláusula where.

var result = dictionary1.Union(dictionary2.Where(k => !dictionary1.ContainsKey(k.Key))).ToDictionary(k => k.Key, v => v.Value)

Nota: No obtendrá una clave duplicada. si habrá alguna clave duplicada, entonces obtendrá la clave del diccionario1.

Opción 3: si desea utilizar ToLookup. entonces obtendrá una búsqueda que puede tener múltiples valores por clave. Podrías convertir esa búsqueda en un diccionario:

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

fuente
6

¿Qué tal agregar una paramssobrecarga?

Además, debe escribirlos IDictionarypara obtener 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);
}
Bryan Watts
fuente
44
Está agregando a las otras respuestas aquí al señalar que puede hacer que la clase DictionaryExtensions sea aún mejor. Quizás esta pregunta debería convertirse en un wiki, o en una clase registrada en git ....
Chris Moschini
5

Teniendo en cuenta el rendimiento de las búsquedas y eliminaciones de claves del diccionario, ya que son operaciones hash, y teniendo en cuenta 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, en mi humilde opinión.

    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á trabajando en una aplicación multiproceso y su diccionario debe ser seguro para subprocesos de todos modos, debería hacer 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);
        }
    }

Luego, podría ajustar esto para que maneje una enumeración de diccionarios. De todos modos, estás viendo aproximadamente ~ O (3n) (todas las condiciones son perfectas), ya .Add()que harán un Contains()detrás de escena adicional, innecesario pero prácticamente gratis . No creo que mejore mucho.

Si desea limitar las operaciones adicionales en colecciones grandes, debe resumir el Countde cada diccionario que está a punto de fusionar y establecer la capacidad del diccionario de destino, lo que evita el costo posterior de cambiar el 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 acepté IList<T>este método ... sobre todo porque si acepta un 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 un LINQ diferido declaración.

GoldPaintedLemons
fuente
4

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));
}
toong
fuente
4

La fiesta está bastante muerta ahora, pero aquí hay una versión "mejorada" de user166390 que llegó a mi biblioteca de extensiones. Además 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;
}
gxtaillon
fuente
2

@Tim: debería ser un comentario, pero los comentarios no permiten la edición de 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 @ANeves a la solución de @Andrew Orsich, por lo que MergeLeft se ve así 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;
    }
keni
fuente
Cerca de lo que escribí yo mismo. Esto, ofc, solo funcionará para los Dictionarytipos. Sobrecargas podrían añadirse otros tipos de diccionario ( ConcurrentDictionary, ReadOnlyDictionary, etc) No creo que la creación de una nueva lista, y concat es necesario personalmente. Primero itere la matriz de params, luego itere cada KVP de cada diccionario. No veo un retroceso.
aplastar
Puede usar IDictionary en lugar de Diccionario
Mariusz Jamro
Siempre obtendría un Dictionaryobjeto incluso si utilizara el método de extensión en a ConcurrentDictionary. Eso podría conducir a errores difíciles de rastrear.
Marcel
2

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));
Cruces
fuente
Perdón por el voto negativo @Cruces, pero su primer ejemplo duplica la respuesta stackoverflow.com/a/6695211/704808 y su segundo ejemplo sigue descartando el IEnumerable <KeyValuePair <string, string >> expandido después de agregar cada elemento mergee.
Weir
2

Me asustó ver respuestas complejas, siendo nuevo en C #.

Aquí hay algunas respuestas simples.
Fusionando d1, d2, etc., diccionarios 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, vea otras respuestas.
Espero que haya ayudado.

Manohar Reddy Poreddy
fuente
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; });
}

Puede omitir / ignorar (predeterminado) o sobrescribir los duplicados: y Bob es su tío, siempre que no sea demasiado quisquilloso con el rendimiento de Linq, sino que prefiera un código de mantenimiento conciso como lo hago yo: en cuyo caso puede eliminar el MergeKind.SkipDuplicates predeterminado para imponer ¡una opción para la persona que llama y hacer que el desarrollador conozca cuáles serán los resultados!

mattjs
fuente
Respuesta refinada a continuación sin enumeración binaria innecesaria cuando un bool hará ...
mattjs
2

Tenga en cuenta que si usa un método de extensión llamado 'Agregar', puede usar inicializadores de colección para combinar tantos diccionarios como sea necesario de esta manera:

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" }
};
Andy
fuente
1

Fusionar usando un método de extensión. No arroja excepciones cuando hay claves duplicadas, sino que las reemplaza 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);
Andrew Mikhailov
fuente
1

Simplificado del uso en comparación con mi respuesta anterior con un valor predeterminado de fusión no destructiva si existe o sobrescribir completamente si es verdadero en lugar de usar una enumeración. Todavía se adapta a mis propias necesidades sin que se requiera ningún código más elegante:

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;
        });
    }
}
mattjs
fuente
0

Fusionar usando un elemento EqualityComparerque asigna elementos para compararlos con un valor / tipo diferente. Aquí asignaremos 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);
BSharp
fuente
0

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 entradas duplicadas "y" gana.

jtroconisa
fuente
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;
        }
mattylantz
fuente
0

Una versión de @ user166390 responde con un IEqualityComparerparámetro agregado para permitir la comparación de teclas sin distinción entre mayúsculas y minúsculas.

    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;
    }
gorilapower
fuente
0
fromDic.ToList().ForEach(x =>
        {
            if (toDic.ContainsKey(x.Key))
                toDic.Remove(x.Key);
            toDic.Add(x);
        });
softwarevamp
fuente