5

I have a list of dictionaries:

L = [{0:1,1:7,2:3,4:8},{0:3,2:6},{1:2,4:6}....{0:2,3:2}]. 

As you can see, the dictionaries have different length. What I need is to add missing keys:values to every dictionary to make them being with the same length:

L1 = [{0:1,1:7,2:3,4:8},{0:3,1:0,2:6,3:0,4:0},{0:0, 1:2,3:0,4:6}....{0:2,1:0,2:0,3:2,4:0}], 

Means to add zeros for missing values. The maximum length isn't given in advance, so one may get it only iterating through the list.

I tried to make something with defaultdicts, like L1 = defaultdict(L) but it seems I don't understand properly how does it work.

Remi Guan
  • 21,506
  • 17
  • 64
  • 87
Polly
  • 1,057
  • 5
  • 14
  • 23

6 Answers6

8

You'll have to make two passes: 1 to get the union of all keys, and another to add the missing keys:

max_key = max(max(d) for d in L)
empty = dict.fromkeys(range(max_key + 1), 0)
L1 = [dict(empty, **d) for d in L]

This uses an 'empty' dictionary as a base to quickly produce all keys; a new copy of this dictionary plus an original dictionary produces the output you want.

Note that this assumes your keys are always sequential. If they are not, you can produce the union of all existing keys instead:

empty = dict.fromkeys(set().union(*L), 0)
L1 = [dict(empty, **d) for d in L]

Demo:

>>> L = [{0: 1, 1: 7, 2: 3, 4: 8}, {0: 3, 2: 6}, {1: 2, 4: 6}, {0: 2, 3: 2}]
>>> max_key = max(max(d) for d in L)
>>> empty = dict.fromkeys(range(max_key + 1), 0)
>>> [dict(empty, **d) for d in L]
[{0: 1, 1: 7, 2: 3, 3: 0, 4: 8}, {0: 3, 1: 0, 2: 6, 3: 0, 4: 0}, {0: 0, 1: 2, 2: 0, 3: 0, 4: 6}, {0: 2, 1: 0, 2: 0, 3: 2, 4: 0}]

or the set approach:

>>> empty = dict.fromkeys(set().union(*L), 0)
>>> [dict(empty, **d) for d in L]
[{0: 1, 1: 7, 2: 3, 3: 0, 4: 8}, {0: 3, 1: 0, 2: 6, 3: 0, 4: 0}, {0: 0, 1: 2, 2: 0, 3: 0, 4: 6}, {0: 2, 1: 0, 2: 0, 3: 2, 4: 0}]

The above approach to merge two dictionaries into a new one with dict(d1, **d2) always works in Python 2. In Python 3 additional constraints have been set on what kind of keys you can use this trick with; only string keys are allowed for the second dictionary. For this example, where you have numeric keys, but you can use dictionary unpacking instead:

{**empty, **d}  # Python 3 dictionary unpacking

That'll work in Python 3.5 and newer.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • The union of dictionary views line `dict(d.items() | empty.items())` is acting erratic for me (replacing random values with the empty value). I can't figure out why but this line can be replaced with `[{**empty, **d} for d in L]` in Python3.8 or lower or `[empty | d for d in L]` in Python3.9. – arsalanQ Oct 07 '21 at 19:15
  • @arsalanQ: it absolutely can be replaced with `{**empty, **d}`, but that syntax wasn't available yet 6 years ago (Python 3.5 had only _just_ been released when this answer was first written). – Martijn Pieters Oct 29 '21 at 12:42
3

Maybe not the most elegant solution, but should be working:

L = [{0:1,1:7,2:3,4:8},{0:3,2:6},{1:2,4:6},{0:2,3:2}]

alldicts = {}
for d in L:
    alldicts.update(d)

allkeys = alldicts.keys()

for d in L:
    for key in allkeys:
        if key not in d:
            d[key] = 0

print(L)
elzell
  • 2,228
  • 1
  • 16
  • 26
3

a bit of caution: changes L

>>> allkeys = frozenset().union(*L)
>>> for i in L:
...    for j in allkeys:
...        if j not in i:
...            i[j]=0

>>> L
[{0: 1, 1: 7, 2: 3, 3: 0, 4: 8}, {0: 3, 1: 0, 2: 6, 3: 0, 4: 0}, {0: 0, 1: 2, 2:
 0, 3: 0, 4: 6}, {0: 2, 1: 0, 2: 0, 3: 2, 4: 0}]
Ayush
  • 3,695
  • 1
  • 32
  • 42
  • 1
    Why all the `list()` calls? `j in allkeys` would be *much* faster if you kept it a set. And you can create a union of all the keys with `set().union(*L)`. – Martijn Pieters Nov 25 '15 at 07:58
  • 1
    Next, you could use set intersections to find missing keys and avoid `not j in i` (should really be `j not in i`) tests: `for missing in allkeys.difference(i):` – Martijn Pieters Nov 25 '15 at 07:59
  • 1
    Next, you could use set intersections to find missing keys and avoid `not j in i` (should really be `j not in i`) tests: `for missing in allkeys.difference(i):` – Martijn Pieters Nov 25 '15 at 07:59
  • sorry, noob mistakes... will update after having a look at `set` docs – Ayush Nov 25 '15 at 08:01
2

This is only a solution, but I think it's simple and straightforward. Note that it modifies the dictionaries in place, so if you want them to be copied, let me know and I'll revise accordingly.

keys_seen = []
for D in L:  #loop through the list
    for key in D.keys():  #loop through each dictionary's keys
        if key not in keys_seen:  #if we haven't seen this key before, then...
            keys_seen.append(key)  #add it to the list of keys seen

for D1 in L:  #loop through the list again
    for key in keys_seen:  #loop through the list of keys that we've seen
        if key not in D1:  #if the dictionary is missing that key, then...
            D1[key] = 0  #add it and set it to 0
El'endia Starman
  • 2,204
  • 21
  • 35
1

This is quick and slim:

missing_keys = set(dict1.keys()) - set(dict2.keys())
for k in missing_keys:
    dict1[k] = dict2[k]
n1nj4
  • 511
  • 5
  • 13
  • This solution is not sufficient: The question has more than two dictionaries involved. – Lutz Prechelt Jun 07 '21 at 13:08
  • true it doesn't meet the OP requirement, but it's elegant and easy to understand, and it could be rewritten to be called iteratively over the set of dictionaries... it's definitely a nice solution for assigning missing values in a dictionary from a default definition. – Gruff May 04 '22 at 03:04
0

Unless None is a valid value for a dictionary key you have herein is a great solution for you

L = [{0: 1, 1: 7, 2: 3, 4: 8}, {0: 3, 2: 6}, {1: 2, 4: 6}, {0: 2, 3: 2}]
for i0, d0 in enumerate(L[:-1]):
    for d1 in L[i0:]:
        _ = [d0.__setitem__(k,d1[k]) for k in d1 if d0.get(k,None) is None]
        _ = [d1.__setitem__(k,d0[k]) for k in d0 if d1.get(k,None) is None]

print(L)
>>> [{0: 1, 1: 7, 2: 3, 3: 2, 4: 8}, {0: 3, 1: 2, 2: 6, 3: 2, 4: 6}, {0: 2, 1: 2, 2: 3, 3: 2, 4: 6}, {0: 2, 1: 7, 2: 3, 3: 2, 4: 8}]
Cobry
  • 4,348
  • 8
  • 33
  • 49