Как сгруппировать словари по двум значениям? - PullRequest
0 голосов
/ 22 мая 2019

Как сгруппировать список словарей как список уникальных словарей на основе нескольких ключевых элементов?

in = [{'location': 'eastus', 'sku': 'S', 'term': 'P1', 'scope': '1'},
         {'location': 'india', 'sku': 'a', 'term': 'P1', 'scope': '2'},
         {'location': 'eastus', 'sku': 'S', 'term': 'P3', 'scope': '3'},
         {'location': 'india', 'sku': 'f', 'term': 'P1', 'scope': '4'},
         {'location': 'japan', 'sku': 'a', 'term': 'P1', 'scope': '5'},
         {'location': 'india', 'sku': 'a', 'term': 'P3', 'scope': '6'}
      ]

Ключевыми элементами являются 'location' и 'sku'. Я хочу сгруппировать словари, имеющие одинаковые ключевые элементы, в один текст, где остальные ключи с различным значением войдут в список отдельные словари внутри того же самого диктанта.

Ожидаемый результат:

out = [{'location': 'eastus', 'sku': 'S', 'new_key': [
                                                     {'term': 'P1', 'scope': '1'}, 
                                                     {'term': 'P3', 'scope': '3'}
                                                    ]},
           {'location': 'india', 'sku': 'a', 'new_key': [
                                                     {'term': 'P1', 'scope': '2'},
                                                     {'term': 'P3', 'scope': '6'}
                                                    ]},
           {'location': 'india', 'sku': 'f', 'term': 'P1', 'scope': '4'},
           {'location': 'japan', 'sku': 'a', 'term': 'P1', 'scope': '5'},
        ]

Я пытался это с необходимыми изменениями, но у нас есть лучшее и более точное решение?

Ответы [ 3 ]

1 голос
/ 22 мая 2019

Использование itertools.groupby

Пример:

from itertools import groupby

data = [{'location': 'eastus', 'sku': 'S', 'term': 'P1', 'scope': '1'},
         {'location': 'india', 'sku': 'a', 'term': 'P1', 'scope': '2'},
         {'location': 'eastus', 'sku': 'S', 'term': 'P3', 'scope': '3'},
         {'location': 'india', 'sku': 'f', 'term': 'P1', 'scope': '4'},
         {'location': 'japan', 'sku': 'a', 'term': 'P1', 'scope': '5'},
         {'location': 'india', 'sku': 'a', 'term': 'P3', 'scope': '6'}
      ]
result = []
for k, v in groupby(sorted(data, key=lambda x: (x["location"], x["sku"])), lambda x: (x["location"], x["sku"])):
    temp = dict(zip(('location', 'sku'), k))
    sub_value = list(v)
    if len(sub_value) == 1:
        temp.update(sub_value[0])
    else:
        temp.update({'new_key': sub_value})
    result.append(temp)

print(result)

Выход:

[{'location': 'eastus',
  'new_key': [{'location': 'eastus', 'scope': '1', 'sku': 'S', 'term': 'P1'},
              {'location': 'eastus', 'scope': '3', 'sku': 'S', 'term': 'P3'}],
  'sku': 'S'},
 {'location': 'india',
  'new_key': [{'location': 'india', 'scope': '2', 'sku': 'a', 'term': 'P1'},
              {'location': 'india', 'scope': '6', 'sku': 'a', 'term': 'P3'}],
  'sku': 'a'},
 {'location': 'india', 'scope': '4', 'sku': 'f', 'term': 'P1'},
 {'location': 'japan', 'scope': '5', 'sku': 'a', 'term': 'P1'}]
0 голосов
/ 22 мая 2019

Вы можете сделать это с помощью вложенных списков и некоторых неприятных лямбд:

from itertools import groupby
import pprint

pp=pprint.PrettyPrinter()

data = [{'location': 'eastus', 'sku': 'S', 'term': 'P1', 'scope': '1'},
         {'location': 'india', 'sku': 'a', 'term': 'P1', 'scope': '2'},
         {'location': 'eastus', 'sku': 'S', 'term': 'P3', 'scope': '3'},
         {'location': 'india', 'sku': 'f', 'term': 'P1', 'scope': '4'},
         {'location': 'japan', 'sku': 'a', 'term': 'P1', 'scope': '5'},
         {'location': 'india', 'sku': 'a', 'term': 'P3', 'scope': '6'}]

pp.pprint([(lambda k,g:k.update({"new_key":[(lambda i:i.pop("location") and i.pop("sku") and i)(item) for item in g]}) or k)(k,g) for k,g in groupby(sorted(data,key=lambda i:(i["location"],i["sku"])), lambda i:{"location":i["location"],"sku":i["sku"]})])

Тест здесь: https://ideone.com/24bjKw. Однако этот процесс уничтожает оригинал data в процессе (вызовы i.pop() работают с оригинальными диктовками).

Цепочка ступеней во внутренних лямбдах - единственная сложная часть:

lambda i:i.pop("location") and i.pop("sku") and i

использует and, потому что pop возвращает найденное, поэтому оценка короткого замыкания не может помешать, и i будет фактическим значением выражения. (Если возникнет сомнение в pop() поиске ключей, это может быть i.pop("...",True))

lambda k,g:k.update(...) or k

нужен or, потому что update() возвращает None (и, таким образом, and приведет к короткому замыканию здесь).

Остальное просто печатать. К сожалению, я не имею ни малейшего представления о том, чтобы отформатировать его в более привлекательную форму.

0 голосов
/ 22 мая 2019

Я изменил приведенный выше ответ, и он дает мне ожидаемый результат. Кстати, спасибо @ rakesh

    from itertools import groupby
    result = []
    keys = ('location', 'sku')
    for k, v in groupby(sorted(lst, key=lambda x: (x["location"], x["sku"])), lambda x: (x["location"], x["sku"])):
        temp = dict(zip(keys, k))
        sub_value = list(v)
        if len(sub_value) == 1:
            temp.update({'new_key': sub_value[0]})
            list(map(temp['new_key'].pop, keys))
        else:
            temp.update({'new_key': sub_value})
            for i in temp['new_key']:
                list(map(i.pop, keys))
        result.append(temp)
    print(result)
...