Fusion de dictionnaires en C#

Quelle est la meilleure façon de fusionner 2 dictionnaires ou plus ( Dictionary<T1,T2> ) en C#? (3.0 caractéristiques comme LINQ sont très bien).

je pense à une signature de méthode du genre:

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: a Obtenu un cool solution de JaredPar et Jon Skeet, mais je pensais à quelque chose qui gère les doubles des clés. En cas de collision, peu importe quelle valeur est sauvegardée le dict tant que c'est cohérent.

384
demandé sur orip 2008-11-16 20:39:56

20 réponses

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

qui explosera si vous obtenez un double des clés.

EDIT: si vous utilisez ToLookup alors vous obtiendrez un ID favori qui peut avoir plusieurs valeurs par clé. Vous pourrait puis convertir en un 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 laid - et inefficace - mais c'est le plus rapide manière de le faire en termes de code. (Je n'ai pas testé, certes.)

vous pourriez écrire votre propre méthode D'extension ToDictionary2 bien sûr (avec un meilleur nom, mais je n'ai pas le temps d'y penser maintenant) - ce n'est pas très difficile à faire, juste en écrasant (ou en ignorant) les clés dupliquées. Le point important (à mon avis) est D'utiliser SelectMany, et de réaliser qu'un dictionnaire soutient itération sur ses paires clé/valeur.

247
répondu Jon Skeet 2011-03-21 21:51:19

je le ferais comme ceci:

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

Simple et facile. Selon ce billet de blog c'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 va de soi qu'il y aura une exception s'il y a des doublons, vous devrez donc vérifier avant de fusionner.

190
répondu Jonas Stensved 2017-06-30 21:34:07

eh Bien, je suis en retard à la fête, mais voici ce que j'utilise. Il n'explose pas s'il y a plusieurs touches (les touches"rightter" remplacent les touches "lefter"), peut fusionner un certain nombre de dictionnaires (si désiré) et préserve 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;
    }

}
89
répondu Andrew Orsich 2012-04-10 12:22:41

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;
}
41
répondu orip 2008-11-16 17:46:45
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);
17
répondu David Osborn 2010-04-06 15:28:54

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);
}
17
répondu JaredPar 2011-10-03 07:25:17

les travaux suivants 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
répondu Ethan Reesor 2015-05-05 13:44:02

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
répondu Andrew Harry 2015-02-11 22:56:08

Que Diriez-vous d'ajouter une surcharge params ?

vous devez aussi les taper comme IDictionary pour un maximum de flexibilité.

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
répondu Bryan Watts 2008-11-16 17:55:53

je suis très en retard à la partie et peut-être manquer quelque chose, mais si soit il n'y a pas de clés dupliquées ou, comme le dit L'OP, "en cas de collision, peu importe quelle valeur est sauvegardée dans le dict tant qu'elle est cohérente," qu'est-ce qui ne va pas avec celle-ci (fusionner 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 je manque quelque chose. 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 dans les tests, donc j'aimerais savoir maintenant si j'oublie quelque chose, au lieu de le découvrir plus tard.

6
répondu codingatty 2014-07-08 05:27:59

compte tenu de la exécution de recherche de clés de dictionnaire et supprime comme il s'agit d'opérations de hachage, et considérant le libellé de la question était meilleure façon, je pense que ci-dessous est une approche parfaitement valide, et les autres sont un peu trop compliqué, 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);
        }
    }

ou si vous travaillez dans une application multithreaded et votre dictionnaire doit être thread safe de toute façon, vous devriez faire ceci:

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

vous pourriez alors envelopper ceci pour le faire gérer une énumération de dictionnaires. Peu importe, vous regardez environ ~o(3n) (toutes les conditions étant parfaites), depuis le .Add() fera un supplémentaire, inutile mais pratiquement libre, Contains() dans les coulisses. Je ne pense pas qu'il obtient beaucoup mieux.

si vous voulez limiter les opérations supplémentaires sur les grandes collections, vous devez résumer le Count de chaque dictionnaire vous êtes sur le point de fusionner et de mettre la capacité du dictionnaire cible à cela, ce qui évite le coût plus tard de redimensionner. Ainsi, le produit final est quelque chose comme cela...

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

Notez que j'ai pris dans un IList<T> à cette méthode... surtout parce que si vous prenez un IEnumerable<T> , vous vous êtes ouvert à plusieurs énumérations du même ensemble, ce qui peut être très coûteux si vous avez votre collection de dictionnaires à partir d'une déclaration de LINQ différé.

5
répondu GoldPaintedLemons 2014-10-13 19:53:53

basé sur les réponses ci-dessus, mais en ajoutant un Func-paramètre pour laisser l'appelant 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
répondu toong 2013-02-20 10:28:25

la partie est à peu près morte maintenant, Mais voici une version" améliorée " de l'utilisateur 166390 qui a fait son chemin dans ma bibliothèque d'extension. En dehors de quelques 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
répondu gxtaillon 2013-04-11 20:00:23

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

Note: j'ai appliqué la modification de @ANeves à la solution de @Andrew Orsich, donc la MergeLeft ressemble à ceci maintenant:

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
répondu keni 2015-03-22 04:22:47

je sais que c'est une vieille question, mais puisque nous avons maintenant LINQ vous pouvez le faire dans 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
répondu Cruces 2017-01-26 17:05:32

Fusion utilisant une méthode d'extension. Il ne jette pas l'exception quand il y a des clés dupliquées, mais remplace ces clés par des clés du second 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;
    }
}

Utilisation:

Dictionary<string, string> merged = first.Merge(second);
1
répondu Andrew Mikhailov 2013-11-08 12:42:21

la Fusion à l'aide d'un EqualityComparer que les cartes des éléments de comparaison à une valeur différente/type. Ici, nous allons faire la carte de KeyValuePair (type d'article 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();
    }
}

Utilisation:

// 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
répondu BSharp 2014-05-29 21:03:38

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
répondu jtroconisa 2017-11-12 16:04:45

a eu peur de voir des réponses complexes, être nouveau à C#.

Voici quelques réponses simples.

Fusion de d1, d2, et ainsi de suite.. dictionnaires et manier les 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 autres réponses.

Espérons que cela a aidé.

0
répondu Manohar Reddy Poreddy 2018-09-05 12:33:17
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 soit passer/ignorer (par défaut) ou écraser les doublons: et votre oncle de Bob à condition que vous n'êtes pas trop pointilleux sur les performances de Linq, mais préférez le code maintenable concis comme je le fais: dans ce cas, vous pouvez supprimer le MergeKind par défaut.SkipDuplicates pour forcer un choix pour l'appelant et rendre le développeur conscient de ce que les résultats seront!

-1
répondu mattjs 2018-10-02 09:34:07