web-dev-qa-db-fra.com

Combinez deux dictionnaires avec LINQ

Ma question a été signalée comme un doublon possible de cette question: Comment combiner deux dictionnaires sans bouclage?

Je crois que ma question est différente parce que je demande comment combiner deux dictionnaires d’une manière particulière: je veux tous les éléments de Dictionary1 ainsi que tous les éléments de Dictionary2 qui ne figurent pas (c’est-à-dire que la clé n’existe pas) dans Dictionary1.

J'ai deux dictionnaires comme celui-ci:

var d1 = new Dictionary<string,object>();
var d2 = new Dictionary<string,object>();

d1["a"] = 1;
d1["b"] = 2;
d1["c"] = 3;

d2["a"] = 11;
d2["e"] = 12;
d2["c"] = 13;

Je voudrais les combiner dans un nouveau dictionnaire (techniquement, il ne doit pas s'agir d'un dictionnaire, il pourrait s'agir simplement d'une séquence de KeyValuePairs) de sorte que la sortie contienne tous les KeyValuePairs de d1 et uniquement les KeyValuePairs de d2 dont Key n'apparaît pas dans d1.

Conceptuellement:

var d3 = d1.Concat(d2.Except(d1))

Mais cela me donne tous les éléments de d1 et d2.

On dirait que cela devrait être évident, mais il me manque quelque chose.

21
wageoghe

Lorsque vous utilisez Except par défaut, il utilise le comparateur d'égalité par défaut qui, pour le type KeyValuePair, compare à la fois les clés et les valeurs. Vous pourriez cette approche à la place:

var d3 = d1.Concat(d2.Where(kvp => !d1.ContainsKey(kvp.Key)));
36
Mark Byers
var d3 = d1.Concat(d2.Where(kvp => ! d1.ContainsKey(kvp.Key)))
           .ToDictionary(x => x.Key, x => x.Value);

Cela fonctionne pour moi.

7
SP007

Eh bien, je ne sais pas s'il s'agit d'une nouvelle fonctionnalité de LinQ, mais c'est exactement ce que fait .Union():

var d3 = d1.Union(d2);

Bien sûr, avec les dictionnaires, vous devrez donner un comparateur d'égalité personnalisé pour ne faire correspondre que les clés:

class KeyValuePairComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>
{
    public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
    {
        return x.Key.Equals(y.Key);
    }
    public int GetHashCode(KeyValuePair<TKey, TValue> x)
    {
        return x.GetHashCode();
    }
}

et alors :

var d3 = d1.Union(d2, new KeyValuePairComparer<string, object>());

Avec votre exemple, la sortie serait (testée en C # interactive):

> d1.Union(d2, new KeyValuePairComparer<string, object>())
UnionIterator { { "a", 1 }, { "b", 2 }, { "c", 3 }, { "e", 12 } }

Notez la différence:

> d2.Union(d1, new KeyValuePairComparer<string, object>())
UnionIterator { { "a", 11 }, { "e", 12 }, { "c", 13 }, { "b", 2 } }
2
Thibault Lemaire

Jon Skeet (comme d'habitude) a une méthode d'extension vous permettant de faire cela: Puis-je spécifier mon comparateur de type explicite en ligne?

2
DaveShaw

Une autre solution utilisant votre propre variable IEqualityComparer, comme dans les réponses de @bitxwise et @DaveShaw, mais n'utilise pas Except(), ce qui simplifie un peu la tâche:

var d3 = d1.Concat(d2).Distinct(new MyComparer());
1
BSharp

Vous pouvez également utiliser votre propre IEqualityComparer. Exemple ci-dessous:

public class MyComparer : IEqualityComparer<KeyValuePair<string,string>> {
    public bool Equals(KeyValuePair<string, string> x, KeyValuePair<string, string> y) {
        return x.Key.Equals(y.Key);
    }

    public int GetHashCode(KeyValuePair<string, string> obj) {
        return obj.Key.GetHashCode();
    }
}

...

Dictionary<string, string> d1 = new Dictionary<string, string>();
d1.Add("A", "B");
d1.Add("C", "D");

Dictionary<string, string> d2 = new Dictionary<string, string>();
d2.Add("E", "F");
d2.Add("A", "D");
d2.Add("G", "H");

MyComparer comparer = new MyComparer();

var d3 = d1.Concat(d2.Except(d1, comparer));
foreach (var a in d3) {
    Console.WriteLine("{0}: {1}", a.Key, a.Value);
}
1
bitxwise