web-dev-qa-db-fra.com

Fusion de dictionnaires en C #

Quel est le meilleur moyen de fusionner deux ou plusieurs dictionnaires (Dictionary<T1,T2>) en C #? (Les fonctionnalités 3.0 telles que LINQ conviennent parfaitement).

Je pense à une signature de méthode du type:

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);

ou

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);

EDIT: J'ai une solution cool de JaredPar et Jon Skeet, mais je pensais à quelque chose qui gère les clés en double. En cas de collision, peu importe la valeur sauvegardée dans le dict tant qu'elle est cohérente.

407
orip

Cela dépend en partie de ce que vous voulez faire si vous rencontrez des doublons. Par exemple, vous pourriez faire:

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

Cela va exploser si vous obtenez des clés en double.

EDIT: Si vous utilisez ToLookup, vous obtiendrez une recherche pouvant avoir plusieurs valeurs par clé. Vous pourriez puis convertir cela en dictionnaire:

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

C'est un peu moche - et inefficace - mais c'est le moyen le plus rapide de le faire en termes de code. (Je ne l'ai pas testé, certes.)

Vous pouvez bien sûr écrire votre propre méthode d’extension ToDictionary2 (avec un meilleur nom, mais je n’ai pas le temps d’en penser à une maintenant) - ce n’est pas très difficile à faire, il suffit de remplacer (ou d’ignorer) les clés dupliquées. Ce qui est important (à mon sens) est l'utilisation de SelectMany et la prise de conscience qu'un dictionnaire prend en charge l'itération sur ses paires clé/valeur.

260
Jon Skeet

Je le ferais comme ça:

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

Simple et facile. Selon cet article de blog il est encore plus rapide que la plupart des boucles car son implémentation sous-jacente accède aux éléments par index plutôt que par énumérateur (voir cette réponse) .

Il y aura bien sûr une exception s'il y a des doublons, vous devrez donc vérifier avant de fusionner.

215
Jonas Stensved

Eh bien, je suis en retard à la fête, mais voici ce que j'utilise. Il n'explose pas s'il y a plusieurs clés (les touches "plus claires" remplacent les clés "lefter"), il peut fusionner un certain nombre de dictionnaires (si vous le souhaitez) et préserver le type (avec la restriction qu'il nécessite un constructeur public par défaut significatif):

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;
    }

}
91
user166390

La solution triviale serait:

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;
}
44
orip

Essayez ce qui suit

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);
}
20
JaredPar
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
17
David Osborn

Ce qui suit fonctionne pour moi. S'il y a des doublons, il utilisera la valeur 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]);
}
13
Ethan Reesor

Je suis très en retard pour le parti et peut-être manque-t-il quelque chose, mais s'il n'y a pas de clés en double ou, comme le dit le PO, "En cas de collision, la valeur sauvegardée dans le dict n'a pas d'importance cohérent, "qu'est-ce qui ne va pas avec celui-ci (fusion de D2 en D1)? 

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

Cela semble assez simple, peut-être trop simple, je me demande si quelque chose me manque. C'est ce que j'utilise dans un code où je sais qu'il n'y a pas de clés dupliquées. Je suis toujours en phase de test, alors j'aimerais bien savoir maintenant si je néglige quelque chose, au lieu de le savoir plus tard.

9
codingatty

Voici une fonction d'aide que j'utilise:

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);
        }
    }
}
8
Andrew Harry

Pourquoi ne pas ajouter une surcharge params?

En outre, vous devez les saisir avec la variable IDictionary pour une flexibilité maximale.

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);
}
6
Bryan Watts

Sur la base des réponses ci-dessus, mais en ajoutant un paramètre Func pour permettre à l'appelant de gérer les doublons:

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

Le parti est à peu près mort, mais voici une version "améliorée" de user166390 qui a été intégrée à ma bibliothèque d'extension ... .. Outre certains détails, j'ai ajouté un délégué pour calculer la valeur fusionnée.

/// <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;
}
3
gxtaillon

@ Tim: devrait être un commentaire, mais les commentaires ne permettent pas l'édition de code.

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);

Remarque: j'ai appliqué la modification apportée par @ANeves à la solution apportée par @Andrew Orsich. Le MergeLeft ressemble maintenant à ceci:

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;
    }
2
keni

Je sais que c'est une vieille question, mais depuis que nous avons maintenant LINQ, vous pouvez le faire en une seule ligne comme celle-ci.

Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));

ou 

mergee.ToList().ForEach(kvp => merged.Append(kvp));
2
Cruces
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; });
}

Vous pouvez ignorer/ignorer (par défaut) ou écraser les doublons: et Bob est votre oncle à condition de ne pas être trop exigeant quant aux performances de Linq, mais de préférer un code maintenable concis comme je le fais: dans ce cas, vous pouvez supprimer le défaut MergeKind.SkipDuplicates un choix pour l'appelant et informez le développeur de ce que seront les résultats!

1
mattjs

Option 1: Cela dépend de ce que vous voulez faire si vous êtes sûr de ne pas avoir la clé en double dans les deux dictionnaires. que vous pourriez faire:

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

Remarque: Cela provoquera une erreur si vous obtenez des clés en double dans les dictionnaires.

Option 2: Si vous pouvez avoir une clé dupliquée, vous devrez gérer la clé dupliquée avec la clause using de where.

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

Remarque: Il n'y aura pas de clé en double. s'il y aura une clé en double, elle obtiendra la clé de dictionary1.

Option 3: / Si vous souhaitez utiliser ToLookup. alors vous obtiendrez une recherche qui peut avoir plusieurs valeurs par clé. Vous pouvez convertir cette recherche en dictionnaire:

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

Fusion en utilisant une méthode d'extension. Il ne lève pas d'exception lorsqu'il y a des clés en double, mais remplace ces clés par des clés du deuxième dictionnaire.

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;
    }
}

Usage:

Dictionary<string, string> merged = first.Merge(second);
1
Andrew Mikhailov

Vous avez peur de voir des réponses complexes, étant nouveau pour C #.

Voici quelques réponses simples.
Fusion de d1, d2, etc., de dictionnaires et gestion des clés qui se chevauchent ("b" dans les exemples ci-dessous):

Exemple 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
}

Exemple 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
}

Pour des scénarios plus complexes, voir d'autres réponses.
J'espère que cela a aidé.

1

Simplifié de l'utilisation par rapport à ma réponse précédente avec un défaut booléen de fusion non destructive si existant ou écrasant entièrement si vrai plutôt que d'utiliser un enum. Cela répond toujours à mes besoins, sans qu'aucun code plus élaboré ne soit jamais nécessaire:

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;
        });
    }
}
1
mattjs
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
user1883961

Fusion utilisant une EqualityComparer qui mappe les éléments à comparer avec une valeur/un type différent. Ici, nous allons mapper de KeyValuePair (type d'élément lors de l'énumération d'un dictionnaire) à 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();
    }
}

Usage:

// 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
BSharp

ou :

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);
    }

le résultat est une union où, pour les entrées en double, "y" gagne.

0
jtroconisa

Une version de @ user166390 répond avec un paramètre ajouté IEqualityComparer pour permettre la comparaison de clés sans distinction de casse.

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

Notez que si vous utilisez une méthode d'extension appelée "Ajouter", vous devez utiliser des initialiseurs de collection pour combiner autant de dictionnaires que nécessaire, comme ceci:

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