Converting a list of key-value tuples into a dictionary where each key maps to a list of associated values is a common data transformation task.
Using defaultdict (Recommended)
The defaultdict from the collections module provides the cleanest and most efficient solution:
from collections import defaultdict
data = [(1, 'A'), (1, 'B'), (2, 'C'), (2, 'D'), (3, 'E')]
grouped = defaultdict(list)
for key, value in data:
grouped[key].append(value)
print(dict(grouped))Using setdefault (No Imports)
When avoiding imports, setdefault provides similar functionality:
data = [(1, 'A'), (1, 'B'), (2, 'C'), (2, 'D'), (3, 'E')]
grouped = {}
for key, value in data:
grouped.setdefault(key, []).append(value)
print(grouped)The setdefault method returns the existing value for a key or sets and returns the default if the key does not exist.
Grouping with Sets (Unique Values)
To collect only unique values per key, use sets instead of lists:
from collections import defaultdict
data = [(1, 'A'), (1, 'A'), (1, 'B'), (2, 'C')]
grouped = defaultdict(set)
for key, value in data:
grouped[key].add(value)
print(dict(grouped))Grouping Objects by Attribute
Apply the same pattern to group objects:
from collections import defaultdict
class Product:
def __init__(self, name, category):
self.name = name
self.category = category
products = [
Product("Apple", "Fruit"),
Product("Banana", "Fruit"),
Product("Carrot", "Vegetable"),
]
by_category = defaultdict(list)
for product in products:
by_category[product.category].append(product.name)
print(dict(by_category))Using itertools.groupby
For pre-sorted data, groupby offers a functional approach:
from itertools import groupby
data = [(1, 'A'), (1, 'B'), (2, 'C'), (2, 'D')]
data_sorted = sorted(data, key=lambda x: x[0])
grouped = {
key: [v for _, v in group]
for key, group in groupby(data_sorted, key=lambda x: x[0])
}
print(grouped)Avoid the O(N²) Trap
A nested comprehension approach looks elegant but performs terribly:
data = [(1, 'A'), (1, 'B'), (2, 'C')]
grouped = {
k: [v for k2, v in data if k2 == k]
for k, _ in data
}
print(grouped)This iterates through the entire list for every tuple, making it quadratic complexity.
from collections import defaultdict
data = [(1, 'A'), (1, 'B'), (2, 'C')]
grouped = defaultdict(list)
for k, v in data:
grouped[k].append(v)
print(dict(grouped))Grouping Multiple Values
When tuples have more than two elements:
from collections import defaultdict
data = [
('Fruit', 'Apple', 1.50),
('Fruit', 'Banana', 0.75),
('Vegetable', 'Carrot', 0.50),
]
grouped = defaultdict(list)
for category, product, price in data:
grouped[category].append({'product': product, 'price': price})
print(dict(grouped))Aggregating Instead of Collecting
Sometimes you want to aggregate values rather than collect them:
from collections import defaultdict
sales = [('Jan', 100), ('Jan', 150), ('Feb', 200), ('Feb', 50)]
totals = defaultdict(int)
for month, amount in sales:
totals[month] += amount
print(dict(totals))Method Comparison
| Method | Time Complexity | Imports | Best For |
|---|---|---|---|
defaultdict(list) |
O(N) | Yes | General use |
setdefault |
O(N) | No | Import-free code |
groupby |
O(N log N)* | Yes | Pre-sorted data |
| Nested comprehension | O(N²) | No | Never use |
*Includes sorting time
Summary
- Use
defaultdict(list)as your default approach for grouping; it is efficient, readable, and handles missing keys automatically. - Use
setdefaultwhen you want to avoid imports. - Avoid nested comprehensions for grouping as they create O(N²) performance problems.