2

I need a method where I can merge two dicts keeping the max value when one of the keys, value are in both dicts.

dict_a maps "A", "B", "C" to 3, 2, 6

dict_b maps "B", "C", "D" to 7, 4, 1

final_dict map "A", "B", "C", "D" to 3, 7, 6, 1

I did get the job half done but I didn't figure out how to keep the max value for the 'C' key, value pair.

Used itertools chain() or update().

taurus05
  • 2,491
  • 15
  • 28
  • 6
    Welcome to StackOverflow! Here you can ① post your code (as a [mcve]), ② describe what you observe it does (with which input), and ③ describe what you expect to observe instead. This results in a clear question which can be answered. Please take the [tour](https://stackoverflow.com/tour) and read the [How-To-Ask](https://stackoverflow.com/help/how-to-ask) to learn more about this. – Alfe Jan 29 '19 at 12:07
  • You wrote that you "did get the job half done." Could you show us the code of how you did that, so we can help you to improve that code? – Rory Daulton Jan 29 '19 at 12:12
  • import itertools z = dict(itertools.chain(dictOne.items(), dictTwo.items())) – Paulo Sergio Schlogl Jan 29 '19 at 12:20

6 Answers6

4

OK so this works by making a union set of all possible keys dict_a.keys() | dict_b.keys() and then using dict.get which by default returns None if the key is not present (rather than throwing an error). We then take the max (of the one which isn't None).

def none_max(a, b):
    if a is None:
        return b
    if b is None:
        return a
    return max(a, b)

def max_dict(dict_a, dict_b):
   all_keys = dict_a.keys() | dict_b.keys()
   return  {k: none_max(dict_a.get(k), dict_b.get(k)) for k in all_keys}

Note that this will work with any comparable values -- many of the other answers fail for negatives or zeros.


Example: Inputs:

dict_a = {'a': 3, 'b': 2, 'c': 6}

dict_b = {'b': 7, 'c': 4, 'd': 1}

Outputs:

max_dict(dict_a, dict_b)  # == {'b': 7, 'c': 6, 'd': 1, 'a': 3}
FHTMitchell
  • 11,793
  • 2
  • 35
  • 47
1

Here is a working one liner

from itertools import chain

x = dict(a=30,b=40,c=50)
y = dict(a=100,d=10,c=30)

x = {k:max(x.get(k, 0), y.get(k, 0)) for k in set(chain(x,y))}

In[83]: sorted(x.items())
Out[83]: [('a', 100), ('b', 40), ('c', 50), ('d', 10)]

This is going to work in any case, i.e for common keys it will take the max of the value otherwise the existing value from corresponding dict.

Rohit
  • 3,659
  • 3
  • 35
  • 57
1

What about

{
    k:max(
        dict_a.get(k,-float('inf')),
        dict_b.get(k,-float('inf'))
    ) for k in dict_a.keys()|dict_b.keys()
}

which returns

{'A': 3, 'D': 1, 'C': 6, 'B': 7}


With
>>> dict_a = {'A':3, 'B':2, 'C':6}
>>> dict_b = {'B':7, 'C':4, 'D':1}
keepAlive
  • 6,369
  • 5
  • 24
  • 39
1

Extending this so you can have any number of dictionaries in a list rather than just two:

a = {'a': 3, 'b': 2, 'c': 6}
b = {'b': 7, 'c': 4, 'd': 1}
c = {'c': 1, 'd': 5, 'e': 7}

all_dicts = [a,b,c]

from functools import reduce

all_keys = reduce((lambda x,y : x | y),[d.keys() for d in all_dicts])

max_dict = { k : max(d.get(k,0) for d in all_dicts) for k in all_keys }
T Burgis
  • 1,395
  • 7
  • 9
0

If you know that all your values are non-negative (or have a clear smallest number), then this oneliner can solve your issue:

a = dict(a=3,b=2,c=6)
b = dict(b=7,c=4,d=1)
merged = { k: max(a.get(k, 0), b.get(k, 0)) for k in set(a) | set(b) }

Use your smallest-possible-number instead of the 0. (E. g. float('-inf') or similar.)

Alfe
  • 56,346
  • 20
  • 107
  • 159
  • Nice. Thanks Alfe. – Paulo Sergio Schlogl Jan 29 '19 at 12:17
  • or `-math.inf` in modern python. – FHTMitchell Jan 29 '19 at 12:18
  • Do not use [`dict`](https://stackoverflow.com/questions/17097985/dict-vs-in-python-which-is-better/17098017) but `{}` instead. – keepAlive Jan 29 '19 at 12:20
  • @FHTMitchell Click on `dict` above and you will see the why. It points to [`dict()` vs `{ }` in python which is better?](https://stackoverflow.com/questions/17097985/dict-vs-in-python-which-is-better/17098017) – keepAlive Jan 29 '19 at 12:28
  • I saw nothing to convince me either way. For the record I always use `{}` – FHTMitchell Jan 29 '19 at 12:30
  • @FHTMitchell I use CPython very often. And the fact that *using dict() to create dictionaries takes up to 6 times longer and involves more memory allocation* does convince me :) – keepAlive Jan 29 '19 at 12:33
  • 1
    @keepAlive I mean, if you're creating dictionary literals in a loop that might matter... otherwise the difference between 100 ns and 300 ns doesn't make much difference to my life. The clarity does. – FHTMitchell Jan 29 '19 at 12:35
  • @FHTMitchell Sure. Alfe will be notified by our kind discussion Hahaha :) – keepAlive Jan 29 '19 at 12:36
  • I stick with the clarity of `dict()` in this case. Using lots of string literals involves a lot more potential for errors, e. g. things like `{ 'a ': 5 }` and similar. Hard to find stuff which is easily avoided by using `dict()` instead (if possible). – Alfe Jan 29 '19 at 12:51
  • And either way, this here is not about how the example dictionaries are created ;-) It's about the merge routine. – Alfe Jan 29 '19 at 12:52
0

Yet another solution:

a = {"A":3, "B":2, "C":6}
b = {"B":7, "C":4, "D":1}

Two liner:

b.update({k:max(a[k],b[k]) for k in a if b.get(k,'')})
res = {**a, **b}

Or if you don't want to change b:

b_copy = dict(b)
b_copy.update({k:max(a[k],b[k]) for k in a if b.get(k,'')})
res = {**a, **b_copy}

> {'A': 3, 'B': 7, 'C': 6, 'D': 1}
Josh Friedlander
  • 10,870
  • 5
  • 35
  • 75