7

I am trying to create a dictionary from the values in the name_num dictionary where the length of the list is the new key and the name_num dictionary key and value are the value. So:

name_num = {"Bill": [1,2,3,4], "Bob":[3,4,2], "Mary": [5, 1], "Jim":[6,17,4], "Kim": [21,54,35]}

I want to create the following dictionary:

new_dict = {4:{"Bill": [1,2,3,4]}, 3:{"Bob":[3,4,2], "Jim":[6,17,4], "Kim": [21,54,35]}, 2:{"Mary": [5, 1]}}

I've tried many variations, but this code gets me the closest:

for mykey in name_num:
    new_dict[len(name_num[mykey])] = {mykey: name_num[mykey]}

Output:

new_dict = {4:{"Bill": [1,2,3,4]}, 3:{"Jim":[6,17,4]}, 2:{"Mary": [5, 1]}}

I know I need to loop through the code somehow so I can add the other values to key 3.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oedipus
  • 143
  • 1
  • 1
  • 9

3 Answers3

21

This is a good use case for defaultdict:

from collections import defaultdict
name_num = {
    'Bill': [1, 2, 3, 4],
    'Bob': [3, 4, 2],
    'Mary': [5, 1],
    'Jim': [6, 17, 4],
    'Kim': [21, 54, 35],
}

new_dict = defaultdict(dict)
for name, nums in name_num.items():
    new_dict[len(nums)][name] = nums

print(dict(new_dict))

Output:

{
    2: {'Mary': [5, 1]},
    3: {'Bob': [3, 4, 2], 'Jim': [6, 17, 4], 'Kim': [21, 54, 35]},
    4: {'Bill': [1, 2, 3, 4]}
}
Karin
  • 8,404
  • 25
  • 34
  • Karin, nicely done. I'm new to the defaultdict. I suppose once you create a default dictionary, it's just a matter of filling it in with data. – Oedipus Aug 29 '16 at 01:52
  • Yes - a default dict basically allows you to set a default value for a dictionary key if you try to access the key and it doesn't exist yet. It comes in handy quite a bit :) – Karin Aug 29 '16 at 01:53
  • I didn't expect the `defaultdict` to keep the value just because it was accessed – Bergi Aug 29 '16 at 10:50
  • @Bergi That would be inconsistent with mutable values. For example if you had: `d = defaultdict(list); value = d[non_existent_key]; value.append(1)` you'd expect to have the pair `non_existent_key: [1]` in the dictionary, but if `defaultdict` doesn't keep the default value created when accessing you'd always end up with `[]`. The property of `defaultdict` is that when you try to access in any way a non existing key that key is added with the default value. The only thing that doesn't trigger key creation is checking like `key in the_dict`. – Bakuriu Aug 29 '16 at 11:02
  • @Bakuriu: Thanks for the explanation. But indeed I had expected mutation not to work, that's why I commented. In other languages, defaults are only for getting, without implicitly creating the key. – Bergi Aug 29 '16 at 11:09
  • 2
    @Bergi Basically the idea of `defaultdict` was developed when people where using the `setdefault` method of the normal `dict` class. You use it like this: `d.setdefault(key, default_value)`. This is like a get but if the key doesn't exists it adds it and assigns the value of `default_value`. Problem: the function call always creates that default value and can thus be quite slow. `defaultdict` is an optimized dictionary where each get call is basically a `setdefault` call but it can avoid creating the default value when the key exists, for maximum concisesness and speed. – Bakuriu Aug 29 '16 at 11:12
5

Dictionary, associative array or map (many names, basically the same functionality) property is that keys are unique.

The keys you wish to have, which are integers, are not unique if lengths are the same, that's why your code doesn't work. Putting a new value for existing key means replacing the old value.

You have to add key-value pairs to the existing value dictionaries.

for mykey in name_num:
    length = len(name_num[mykey])
    if length in new_dict: # key already present in new dictionary
        new_dict[length][mykey] = name_num[mykey]
    else:
        new_dict[length] = {mykey: name_num[mykey]}

should do the trick

Jezor
  • 3,253
  • 2
  • 19
  • 43
  • 1
    Thanks Jezor. I am new to Python. That clears up a lot of confusion on how to add data to a dictionary. – Oedipus Aug 29 '16 at 01:34
4

Just an alternative to others; you can sort by length and use itertools.groupby:

>>> result = {}
>>> f = lambda t: len(t[1])
>>> for length, groups in itertools.groupby(sorted(name_num.items(), key=f), key=f):
...     result[length] = dict((k, v) for k, v in groups)
>>> print result
{
    2: {'Mary': [5, 1]},
    3: {'Bob': [3, 4, 2], 'Jim': [6, 17, 4], 'Kim': [21, 54, 35]},
    4: {'Bill': [1, 2, 3, 4]}
}

In a worst case scenario where each inner list has a different length, this performs O(n^2) which is fairly inefficient compared to other solutions posted above.

Ozgur Vatansever
  • 49,246
  • 17
  • 84
  • 119