2010-05-11 11 views
12

J'ai deux dictionnaires avec la même structure:Comment faire la somme des valeurs de deux dictionnaires en C#?

Dictionary<string, int> foo = new Dictionary<string, int>() 
{ 
    {"Table", 5 }, 
    {"Chair", 3 }, 
    {"Couch", 1 } 
}; 

Dictionary<string, int> bar = new Dictionary<string, int>() 
{ 
    {"Table", 4 }, 
    {"Chair", 7 }, 
    {"Couch", 8 } 
}; 

Je voudrais résumer les valeurs des dictionnaires ensemble et retourner un troisième dictionnaires avec les clés et les valeurs totales pour chaque touche:

Table, 9 
Chair, 10 
Couch, 9 

Ma solution actuelle est de faire une boucle dans le dictionnaire et de les sortir de cette façon, mais je sais que cette solution n'est pas la plus performante ou la plus lisible. Cependant, je frappe un mur de briques en essayant de trouver une solution dans LINQ.

+0

Est-il garanti que les deux dictionnaires auront le même ensemble de clés? – Carlos

+0

@Carlos dans ce cas, oui. Mais il serait intéressant de voir une solution où les dictionnaires partagent certaines clés et pas d'autres. –

Répondre

12

Ce qui suit est pas la solution la plus efficace (car il traite simplement les dictionnaires comme enumerables), mais il fonctionnera et il est tout à fait clair:

Dictionary<string, int> result = (from e in foo.Concat(bar) 
       group e by e.Key into g 
       select new { Name = g.Key, Count = g.Sum(kvp => kvp.Value) }) 
       .ToDictionary(item => item.Name, item => item.Count); 
+0

@Ben: Merci pour la correction, je viens de le réaliser aussi. –

+0

Modifié votre réponse pour montrer comment récupérer la requête résultante dans un dictionnaire. –

+0

@George: Merci –

4
(from a in foo 
join b in bar on a.Key equals b.Key 
select new { Key = a.Key, Value = a.Value + b.Value }) 
.ToDictionary(a => a.Key,a => a.Value) 

Cela devrait le faire.

EDIT: peut-être plus efficace (pas sûr de savoir comment la jointure est mis en œuvre)

(from a in foo 
let b = bar.ContainsKey(a.Key) ? (int?)bar[a.Key] : null 
select new { Key = a.Key, Value = a.Value + (b != null ? b : 0) } 
).ToDictionary(a => a.Key, a => a.Value) 
+0

Merci pour la réponse; cette réponse aide également si vous voulez calculer des deltas entre les objets. –

4

Si vous avez une garantie de fonte que les deux jeux de clés sont les mêmes:

Dictionary<string, int> Res2 = foo.ToDictionary(orig => orig.Key, orig => orig.Value + bar[orig.Key]); 

Le meilleur que je pouvais venir avec si les clés ne sont pas mêmes:

var AllKeys = foo.Keys.Union(bar.Keys); 
var res3 = AllKeys.ToDictionary(key => key, key => (foo.Keys.Contains(key)?foo[key] : 0) + (bar.Keys.Contains(key)?bar[key] : 0)); 
3

Mmm, je ne sais pas ce qui est plus par fo rmant, mais comment votre solution n'est-elle pas lisible?

Quel est le problème avec

foreach (string key in d1.Keys) 
    { 
    d3.Add(key,d1[key]+d2[key]); 
    } 

? En fait, je pense que c'est plus clair que certaines solutions linq. Même si je ne l'ai pas testé, je pense qu'il pourrait avoir de meilleures performances, puisqu'il n'énumère que les clés dans un dictionnaire et non les valeurs, vous utiliseriez le hachage réel (ou quelle que soit l'implémentation sous-jacente du dictionnaire) pour trouver les valeurs, ce qui est le moyen le plus rapide de les obtenir.

EDIT:

pour la solution où les clés wouldnt toujours les mêmes, si vous voulez seulement obtenir les communes, il vous suffit d'ajouter une ligne;

foreach (string key in d1.Keys) 
    { 
    if(d2.ContainsKey(key) 
     d3.Add(key,d1[key]+d2[key]); 
    } 

EDIT2:

Afin d'obtenir toutes les clés/valeurs si elles ne sont pas les mêmes, alors ce serait comme ceci:

foreach (string key in d1.Keys) 
     { 
     if(d2.ContainsKey(key) 
      d3.Add(key,d1[key]+d2[key]); 
     else 
      d3.Add(key,d1[key]) 
     } 

    foreach (string key in d2.keys) 
     { 
      if(!d1.ContainsKey(key) // only get keys that are unique to d2 
      d3.Add(key,d2[key]); 
     } 
+0

Eh bien, il ya toujours le problème de 'd2' ayant des clés qui ne sont pas dans' d1', bien sûr ... –

+0

@Dan Tao ouais, cela ne fonctionnerait que pour les clés partagées. EDIT: d'accord, j'ai ajouté la solution pour cela: P –

2

Qu'en est-il quelque chose comme ça?

var fooBar = foo.Keys 
    .Union(bar.Keys) 
    .Select(
     key => { 
      int fval = 0, bval = 0; 

      foo.TryGetValue(key, out fval); 
      bar.TryGetValue(key, out bval); 

      return new KeyValuePair<string, int>(key, fval + bval); 
     } 
    ) 
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); 

Au moins c'est (sorte de?) Soignée.

1

J'ai écrit une petite méthode d'extension qui va fusionner une liste de dictionnaires avec des valeurs Int.J'ai utilisé le code de cette question pour le faire afin que je partage

public static Dictionary<TSource, Int32> MergeIntDictionary<TSource>(this ICollection<Dictionary<TSource, Int32>> source) 
    { 
     return source.Aggregate((cur, next) => cur.Concat(next) 
      .GroupBy(o => o.Key) 
      .ToDictionary(item => item.Key, item => item.Sum(o => o.Value))); 
    } 
Questions connexes