Группа и агрегировать список словарей нескольких ключей

Вопрос задан: 1 год назад Последняя активность: 1 год назад
up 14 down

У меня есть список, который включает в себя словари (List[Dict, Dict, ...]), Я хотел бы uniqify список, основанный на двух ключей, но я хочу, чтобы сохранить значение другого ключа в словаре, чтобы убедиться, что я не потерять его, делая список в ключе я хочу сохранить. Я использую Python для кода. Если какое-либо значение Python 3.x, чтобы быть точным.

Давайте предположим, что у меня есть следующий список словарей с тремя ключами: number, favorite, а также color. Я хочу uniqify элементы списка с помощью клавиш 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 даже если он появился дважды в списке, прежде чем он был uniqified. Также отметим, что, когда есть только один элемент для ключа color во втором Словаре, это строка, а не список.

6 ответов

Возможно, для Вашего проекта будут необходимы бесплатные векторные карты. На нашем сайте представлены карты для всех стран.

Реклама

up 15 down accepted

Использование чистого питона, вы можете сделать вставку в 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}]

Это также можно сделать довольно легко с помощью панд 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}]
up 3 down

Решение в чистом 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()]
up 2 down

Или же 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']}]
up 1 down

Вы можете использовать упорядоченный словарь с умолчанию set ценности.1 Затем перебирать список словарей, используя (number, favorite) в качестве ключей. Это работает, так как кортежи hashable и поэтому разрешается использовать в качестве ключей словаря.

Это хорошая практика, чтобы использовать последовательную структуру. Таким образом, вместо того, чтобы строки для отдельных значений и наборы для нескольких, использовать наборы в течение:

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.

up 0 down

Вот один из способов сделать это,

Я построил 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'}}]
up 0 down

Мой друг сделал следующую функцию, чтобы решить эту проблему, без использования каких-либо внешних библиотек:

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, он просто сделал тривиальное uniqify, чтобы получить уникальные результаты из списка. Это не означает, однако, держать один цвет в виде строки, а скорее список с одним элементом.

Ошибка 505

Что-то пошло не так

Попробуйте воспользоваться поиском