62

I have a Python list which holds pairs of key/value:

l = [[1, 'A'], [1, 'B'], [2, 'C']]

I want to convert the list into a dictionary, where multiple values per key would be aggregated into a tuple:

{1: ('A', 'B'), 2: ('C',)}

The iterative solution is trivial:

l = [[1, 'A'], [1, 'B'], [2, 'C']]
d = {}
for pair in l:
    if pair[0] in d:
        d[pair[0]] = d[pair[0]] + tuple(pair[1])
    else:
        d[pair[0]] = tuple(pair[1])

print(d)

{1: ('A', 'B'), 2: ('C',)}

Is there a more elegant, Pythonic solution for this task?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Adam Matan
  • 128,757
  • 147
  • 397
  • 562

7 Answers7

56
from collections import defaultdict

d1 = defaultdict(list)

for k, v in l:
    d1[k].append(v)

d = dict((k, tuple(v)) for k, v in d1.items())

d contains now {1: ('A', 'B'), 2: ('C',)}

d1 is a temporary defaultdict with lists as values, which will be converted to tuples in the last line. This way you are appending to lists and not recreating tuples in the main loop.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
eumiro
  • 207,213
  • 34
  • 299
  • 261
  • Great code! I have a list that has the following values: `list_A = [(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 1, 0) ..... ]` and I need to transform it to a dictionary so that it has as `key` each of the values, ex: `(0, 0, 0, 1)` and as a `value` of the dictionary the frequency (how many times does the specific values appears inside the `list_A`) of the value inside the `list_A`. What modifications should I do in the above code? – just_learning Feb 15 '22 at 14:57
17

Using lists instead of tuples as dict values:

l = [[1, 'A'], [1, 'B'], [2, 'C']]
d = {}
for key, val in l:
    d.setdefault(key, []).append(val)

print(d)

Using a plain dictionary is often preferable over a defaultdict, in particular if you build it just once and then continue to read from it later in your code:

First, the plain dictionary is faster to build and access.

Second, and more importantly, the later read operations will error out if you try to access a key that doesn't exist, instead of silently creating that key. A plain dictionary lets you explicitly state when you want to create a key-value pair, while the defaultdict always implicitly creates them, on any kind of access.

jtlz2
  • 7,700
  • 9
  • 64
  • 114
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • What's the difference between/advantages of using `d.setdefault(key,[]).append(val)` and `d.get(key,[]).append(val)`? – jtlz2 Aug 25 '21 at 13:32
  • 1
    In `setdefault()`'s favour is that using `.get()` doesn't work :\ – jtlz2 Aug 25 '21 at 13:38
  • 1
    @jtlz2 `.get()` only retrieves values from the dictionary. If there is no entry for the given key, get will happily return the new list and allow appending values to that list, but the dictionary won't be updated to actually include the new list, so the new list will simply be garbage collected after appending the new value. – Sven Marnach Aug 25 '21 at 14:51
10

This method is relatively efficient and quite compact:

reduce(lambda x, (k,v): x[k].append(v) or x, l, defaultdict(list))

In Python3 this becomes (making exports explicit):

dict(functools.reduce(lambda x, d: x[d[0]].append(d[1]) or x, l, collections.defaultdict(list)))

Note that reduce has moved to functools and that lambdas no longer accept tuples. This version still works in 2.6 and 2.7.

jtniehof
  • 581
  • 3
  • 8
user2085084
  • 101
  • 1
  • 2
3

Are the keys already sorted in the input list? If that's the case, you have a functional solution:

import itertools

lst = [(1, 'A'), (1, 'B'), (2, 'C')]
dct = dict((key, tuple(v for (k, v) in pairs)) 
           for (key, pairs) in itertools.groupby(lst, lambda pair: pair[0]))
print dct
# {1: ('A', 'B'), 2: ('C',)}
tokland
  • 66,169
  • 13
  • 144
  • 170
  • If you `import operator`, you can write `itertools.groupby(sorted(lst), operator.itemgetter(0))` – eumiro Mar 21 '11 at 14:25
0

I had a list of values created as follows:

performance_data = driver.execute_script('return window.performance.getEntries()')  

Then I had to store the data (name and duration) in a dictionary with multiple values:

dictionary = {}
    for performance_data in range(3):
        driver.get(self.base_url)
        performance_data = driver.execute_script('return window.performance.getEntries()')
        for result in performance_data:
            key=result['name']
            val=result['duration']
            dictionary.setdefault(key, []).append(val)
        print(dictionary)
0

My data was in a Pandas.DataFrame

myDict = dict()

for idin set(data['id'].values):
    temp = data[data['id'] == id]
    myDict[id] = temp['IP_addr'].to_list()
    
myDict

Gave me a Dict of the keys, ID, mappings to >= 1 IP_addr. The first IP_addr is Guaranteed. My code should work even if temp['IP_addr'].to_list() == []

{'fooboo_NaN': ['1.1.1.1', '8.8.8.8']}
Ali Pardhan
  • 194
  • 1
  • 14
0

My two coins for toss into that amazing discussion) I've tried to wonder around one line solution with only standad libraries. Excuse me for the two excessive imports. Perhaps below code could solve the issue with satisfying quality (for the python3):

from functools import reduce
from collections import defaultdict

a = [1, 1, 2, 3, 1]
b = ['A', 'B', 'C', 'D', 'E']

c = zip(a, b)

print({**reduce(lambda d,e: d[e[0]].append(e[1]) or d, c, defaultdict(list))})
Vanya Usalko
  • 405
  • 7
  • 10