Группировать и объединять список словарей по нескольким ключам
У меня есть список, который включает словари (List[Dict, Dict,...]
), я хотел бы унифицировать список на основе двух ключей, но я хочу сохранить значение другого ключа в словаре, чтобы убедиться, что я делаю не потерять его, составив список в ключе, который я хочу сохранить. Я использую Python для кода. Если это имеет какое-либо значение Python 3.x, чтобы быть точным.
Предположим, у меня есть следующий список словарей с тремя ключами: number
, favorite
и color
. Я хочу унифицировать элементы списка, используя number
ключа и favorite
. Однако для словарей с одинаковыми значениями number
и favorite
, я бы хотел добавить список под ключевым color
чтобы убедиться, что у меня есть все color
для одинаковой комбинации number
и favorite
. Этот список также должен быть уникальным, поскольку он не должен нуждаться в повторном color
для той же комбинации. Однако, если в конечном результате есть только один элемент для цвета ключа, это должна быть строка, а не список.
lst = [
{'number': 1, 'favorite': False, 'color': 'red'},
{'number': 1, 'favorite': False, 'color': 'green'},
{'number': 1, 'favorite': False, 'color': 'red'},
{'number': 1, 'favorite': True, 'color': 'red'},
{'number': 2, 'favorite': False, 'color': 'red'}]
Используя вышеупомянутый uniqify, я получил бы следующий результат:
lst = [
{'number': 1, 'favorite': False, 'color': {'red', 'green'}},
{'number': 1, 'favorite': True, 'color': 'red'},
{'number': 2, 'favorite': False, 'color': 'red'},
]
Обратите внимание, что существует только один экземпляр red
где number
равно 1
а favorite
- False
даже если оно появилось дважды в списке до того, как оно было отменено. Также обратите внимание, что когда во втором dict есть только один элемент для color
ключа, это строка, а не список.
Ответы
Ответ 1
Используя чистый python, вы можете вставить в OrderedDict
чтобы сохранить порядок вставки:
from collections import OrderedDict
d = OrderedDict()
for l in lst:
d.setdefault((l['number'], l['favorite']), set()).add(l['color'])
[{'number': k[0], 'favorite': k[1], 'color': v.pop() if len(v) == 1 else v}
for k, v in d.items()]
# [{'color': {'green', 'red'}, 'favorite': False, 'number': 1},
# {'color': 'red', 'favorite': True, 'number': 1},
# {'color': 'red', 'favorite': False, 'number': 2}]
Это также может быть сделано довольно легко с помощью pandas GroupBy
API:
import pandas as pd
d = (pd.DataFrame(lst)
.groupby(['number', 'favorite'])
.color
.agg(set)
.reset_index()
.to_dict('r'))
d
# [{'color': {'green', 'red'}, 'favorite': False, 'number': 1},
# {'color': {'red'}, 'favorite': True, 'number': 1},
# {'color': {'red'}, 'favorite': False, 'number': 2}]
Если требуется условие строки для одного элемента, вы можете использовать
[{'color': (lambda v: v.pop() if len(v) == 1 else v)(d_.pop('color')), **d_}
for d_ in d]
# [{'color': {'green', 'red'}, 'favorite': False, 'number': 1},
# {'color': 'red', 'favorite': True, 'number': 1},
# {'color': 'red', 'favorite': False, 'number': 2}]
Ответ 2
Решением на чистом Python было бы использование defaultdict
с составным ключом. Вы можете использовать это, чтобы объединить ваши ценности. После этого вы можете снова создать список из этого словаря.
from collections import defaultdict
dct = defaultdict([])
for entry in lst:
dct[(entry['number'], entry['favorite'])].append(entry['color'])
lst = [{'number': key[0], 'favorite': key[1], color: value if len(value) > 1 else value[0]}
for key, value in dct.items()]
Ответ 3
Или groupby
itertools
:
import itertools
lst = [
{'number': 1, 'favorite': False, 'color': 'red'},
{'number': 1, 'favorite': False, 'color': 'green'},
{'number': 1, 'favorite': False, 'color': 'red'},
{'number': 1, 'favorite': True, 'color': 'red'},
{'number': 2, 'favorite': False, 'color': 'red'}]
l=[list(y) for x,y in itertools.groupby(sorted(lst,key=lambda x: (x['number'],x['favorite'])),lambda x: (x['number'],x['favorite']))]
print([{k:(v if k!='color' else list(set([x['color'] for x in i]))) for k,v in i[0].items()} for i in l])
Выход:
[{'number': 1, 'favorite': False, 'color': ['green', 'red']}, {'number': 1, 'favorite': True, 'color': ['red']}, {'number': 2, 'favorite': False, 'color': ['red']}]
Ответ 4
Вы можете использовать упорядоченный словарь с set
значениями по умолчанию. 1 Затем итерируйте свой список словарей, используя (number, favorite)
качестве ключей. Это работает, поскольку кортежи могут быть хэшируемыми и поэтому могут использоваться в качестве ключей словаря.
Хорошей практикой является использование последовательной структуры. Таким образом, вместо строк для отдельных значений и наборов для нескольких, используйте наборы повсюду:
from collections import OrderedDict, defaultdict
class DefaultOrderedDict(OrderedDict):
def __missing__(self, k):
self[k] = set()
return self[k]
d = DefaultOrderedDict() # Python 3.7+: d = defaultdict(set)
for i in lst:
d[(i['number'], i['favorite'])].add(i['color'])
res = [{'number': num, 'favorite': fav, 'color': col} for (num, fav), col in d.items()]
print(res)
# [{'color': {'green', 'red'}, 'favorite': False, 'number': 1},
# {'color': {'red'}, 'favorite': True, 'number': 1},
# {'color': {'red'}, 'favorite': False, 'number': 2}]
Если вы настаиваете на наличии разных типов в зависимости от количества цветов, вы можете переопределить понимание списка, чтобы использовать троичное выражение:
res = [{'number': num, 'favorite': fav, 'color': next(iter(col)) if len(col) == 1 else col} \
for (num, fav), col in d.items()]
print(res)
# [{'color': {'green', 'red'}, 'favorite': False, 'number': 1},
# {'color': 'red', 'favorite': True, 'number': 1},
# {'color': 'red', 'favorite': False, 'number': 2}]
1 Этот момент примечателен в версиях Python до 3.7, где словари не могут быть упорядочены при вставке. С Python 3. 7+ вы можете воспользоваться преимуществами упорядочения вставок и просто использовать dict
или подкласс dict
такой как collections.defaultdict
.
Ответ 5
Вот один из способов сделать это,
Сначала я создал dict
используя кортеж в качестве составного ключа, а затем создал новый список из этого dict
. Вы можете написать понимание для дальнейшего сокращения строк и оптимизации, надеюсь, это поможет.
new_dict = {}
for item in lst:
try: # if already exists then append to the list
new_dict.get((item['number'], item['favorite']))
new_dict[(item['number'], item['favorite'])].append(item['color'])
except KeyError: # if it doesn't then create a new entry to that key
new_dict[(item['number'], item['favorite'])] = [item['color']]
final_list = []
for k, v in new_dict.items(): # keep appending dicts to our list
final_list.append({'number': k[0], 'favorite': k[1], 'color':set(v)})
print(final_list)
Выходы:
[{'number': 1, 'favorite': False, 'color': {'green', 'red'}}, {'number': 1, 'favorite': True, 'color': {'red'}}, {'number': 2, 'favorite': False, 'color': {'red'}}]
Ответ 6
Мой друг сделал следующую функцию для решения этой проблемы, без использования каких-либо внешних библиотек:
def uniqifyColors(l):
for elem in l:
for item in l:
if elem['number'] == item['number'] and elem['favorite'] == item['favorite']:
for clr in item['color']:
if clr not in elem['color']:
elem['color'].append(clr)
return l
После использования этой функции Python он просто выполнил тривиальную операцию, чтобы получить уникальные результаты из списка. Однако он не сохраняет один цвет в виде строки, а представляет собой список с одним элементом.