Asked  7 Months ago    Answers:  5   Viewed   128 times

What's the best way to merge 2 or more dictionaries (Dictionary<T1,T2>) in C#? (3.0 features like LINQ are fine).

I'm thinking of a method signature along the lines of:

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

or

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

EDIT: Got a cool solution from JaredPar and Jon Skeet, but I was thinking of something that handles duplicate keys. In case of collision, it doesn't matter which value is saved to the dict as long as it's consistent.

 Answers

34

This partly depends on what you want to happen if you run into duplicates. For instance, you could do:

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

That will throw an exception if you get any duplicate keys.

EDIT: If you use ToLookup then you'll get a lookup which can have multiple values per key. You could then convert that to a dictionary:

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

It's a bit ugly - and inefficient - but it's the quickest way to do it in terms of code. (I haven't tested it, admittedly.)

You could write your own ToDictionary2 extension method of course (with a better name, but I don't have time to think of one now) - it's not terribly hard to do, just overwriting (or ignoring) duplicate keys. The important bit (to my mind) is using SelectMany, and realising that a dictionary supports iteration over its key/value pairs.

Tuesday, June 1, 2021
 
BartmanEH
answered 7 Months ago
49

In the first case, the Union is discarding the duplicate keys because the key/value pairs themselves are equal. In the second case they're not, because a List<String>{"one"} isn't equal to another List<string>{"one"}.

I suspect you want your Union call to use an IEqualityComparer which only takes account of the keys within the dictionary.

Monday, August 16, 2021
 
shivam
answered 4 Months ago
20

However are you trying to merge the values, you will probably want to use one of these options:

D3[1] = D1[1].Union(D2[1]);

or

D3[1] = D1[1].Concat(D2[1]);

Edit - an ugly-looking method for joined merges Linq-style:

foreach (var kvp in D1)
{
    D3[kvp.Key] =
        (from string letter in kvp.Value
        select
            (from IEnumerable<string> list in D2.Values
            where list.Contains(letter)
            select list)
             // Union all the D2 lists containing a letter from D1.
            .Aggregate((aggregated, next) => aggregated.Union(next)))
        // Union all the D2 lists containing all the letter from D1.
        .Aggregate((aggregated, next) => aggregated.Union(next))
        // Convert the unioned letters to a List.
        .ToList();
}

The code keeps the lists in D2, it would be pretty easy to modify the code to remove the matched lists from D2.

Thursday, August 19, 2021
 
Jeff Swensen
answered 4 Months ago
23

Use a defaultdict:

from collections import defaultdict

out = defaultdict(dict)
for name, phonenumber in phone.iteritems():
    out[name]['phone'] = phonenumber
for name, address in addr.iteritems():
    out[name]['address'] = address

For python 3 just replace .iteritems() with .items():

out = defaultdict(dict)
for name, phonenumber in phone.items():
    out[name]['phone'] = phonenumber
for name, address in addr.items():
    out[name]['address'] = address

You do need to loop over both input dictionaries, because you are moving the values to a new dictionary per name.

Friday, August 20, 2021
 
pocketfullofcheese
answered 4 Months ago
26

What you need to do is:

  1. Sort items lexicographically where range key is [r_start,r_end]

  2. Iterate the sorted list and check if current item overlaps with next. If it does extend current item to be r[i].start,r[i+1].end, and goto next item. If it doesn't overlap add current to result list and move to next item.

Here is sample code:

    vector<pair<int, int> > ranges;
    vector<pair<int, int> > result;
    sort(ranges.begin(),ranges.end());
    vector<pair<int, int> >::iterator it = ranges.begin();
    pair<int,int> current = *(it)++;
    while (it != ranges.end()){
       if (current.second > it->first){ // you might want to change it to >=
           current.second = std::max(current.second, it->second); 
       } else {
           result.push_back(current);
           current = *(it);
       }
       it++;
    }
    result.push_back(current);
Sunday, September 5, 2021
 
xrdty
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share