Група Python від


125

Припустимо, що у мене є набір пар даних, де індекс 0 - це значення, а індекс 1 - тип:

input = [
          ('11013331', 'KAT'), 
          ('9085267',  'NOT'), 
          ('5238761',  'ETH'), 
          ('5349618',  'ETH'), 
          ('11788544', 'NOT'), 
          ('962142',   'ETH'), 
          ('7795297',  'ETH'), 
          ('7341464',  'ETH'), 
          ('9843236',  'KAT'), 
          ('5594916',  'ETH'), 
          ('1550003',  'ETH')
        ]

Я хочу згрупувати їх за їх типом (за 1-м індексованим рядком) як такий:

result = [ 
           { 
             type:'KAT', 
             items: ['11013331', '9843236'] 
           },
           {
             type:'NOT', 
             items: ['9085267', '11788544'] 
           },
           {
             type:'ETH', 
             items: ['5238761', '962142', '7795297', '7341464', '5594916', '1550003'] 
           }
         ] 

Як я можу досягти цього ефективним способом?

Відповіді:


153

Зробіть це в 2 дії. Спочатку створіть словник.

>>> input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')]
>>> from collections import defaultdict
>>> res = defaultdict(list)
>>> for v, k in input: res[k].append(v)
...

Потім перетворіть цей словник у очікуваний формат.

>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}]

Це також можливо за допомогою itertools.groupby, але для цього потрібне впорядкування першого введення.

>>> sorted_input = sorted(input, key=itemgetter(1))
>>> groups = groupby(sorted_input, key=itemgetter(1))
>>> [{'type':k, 'items':[x[0] for x in v]} for k, v in groups]
[{'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}]

Зверніть увагу, що обидві не поважають початковий порядок клавіш. Якщо вам потрібно зберегти замовлення, вам потрібен OrdersDict.

>>> from collections import OrderedDict
>>> res = OrderedDict()
>>> for v, k in input:
...   if k in res: res[k].append(v)
...   else: res[k] = [v]
... 
>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}]

Як це можна зробити, якщо вхідний кортеж має один ключ та два чи більше значення, як-от так: [('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]де останній елемент кортежа є ключовим, а перші два - значеннями. Результат повинен бути таким: result = [{type: 'KAT', items: [('11013331', червоний), ('9085267', синій)]}}]
користувач1144616

1
from operator import itemgetter
Бауман

1
крок 1 можна здійснити без імпорту:d= {}; for k,v in input: d.setdefault(k, []).append(v)
ecoe

Я працюю над програмою MapReduce в python, просто цікаво, чи є якийсь спосіб згрупувати за значеннями в списку, не маючи стосунків зі словниками чи зовнішньою бібліотекою, наприклад, пандами? Якщо ні, то як я можу позбутися предметів і ввести результат?
Курош

54

Вбудований itertoolsмодуль Python насправді має groupbyфункцію, але для цього елементи, які потрібно згрупувати, спочатку повинні бути відсортовані таким чином, щоб елементи, які слід згрупувати, були суміжними у списку:

from operator import itemgetter
sortkeyfn = itemgetter(1)
input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), 
 ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), 
 ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')] 
input.sort(key=sortkeyfn)

Тепер вхід виглядає так:

[('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'),
 ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH'), ('11013331', 'KAT'),
 ('9843236', 'KAT'), ('9085267', 'NOT'), ('11788544', 'NOT')]

groupbyповертає послідовність 2-х кортезів форми (key, values_iterator). Ми хочемо перетворити це на список диктовок, де "тип" є ключем, а "items" - це список 0-х елементів кортежів, повернутих значень_iterator. Подобається це:

from itertools import groupby
result = []
for key,valuesiter in groupby(input, key=sortkeyfn):
    result.append(dict(type=key, items=list(v[0] for v in valuesiter)))

Тепер resultміститься бажаний вислів, як зазначено у вашому запитанні.

Ви можете врахувати, що просто скласти з цього один диктат, введений за типом і кожне значення, що містить список значень. У вашій поточній формі, щоб знайти значення для певного типу, вам доведеться перебрати список, щоб знайти дік, що містить відповідний ключ 'type', а потім отримати з нього елемент 'items'. Якщо ви використовуєте один дикт замість списку однокласних диктовок, ви можете знайти елементи для певного типу за допомогою одного клавішного пошуку в основний дікт. Використовуючи groupbyце, виглядатиме так:

result = {}
for key,valuesiter in groupby(input, key=sortkeyfn):
    result[key] = list(v[0] for v in valuesiter)

resultтепер містить цей дикт (це схоже на проміжний resвирок за замовчуванням у відповіді @ KennyTM):

{'NOT': ['9085267', '11788544'], 
 'ETH': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 
 'KAT': ['11013331', '9843236']}

(Якщо ви хочете зменшити це до однолінійного, ви можете:

result = dict((key,list(v[0] for v in valuesiter)
              for key,valuesiter in groupby(input, key=sortkeyfn))

або використовуючи новомодну форму розуміння дикту:

result = {key:list(v[0] for v in valuesiter)
              for key,valuesiter in groupby(input, key=sortkeyfn)}

Я працюю над програмою MapReduce в python, просто цікаво, чи є якийсь спосіб згрупувати за значеннями в списку, не маючи стосунків зі словниками чи зовнішньою бібліотекою, наприклад, пандами? Якщо ні, то як я можу позбутися предметів і ввести результат?
Курош

@Kourosh - Опублікуйте як нове запитання, але обов'язково вкажіть, що ви маєте на увазі під "позбутися предметів та ввести мій результат" та "не маючи справу зі словниками".
PaulMcG

7

Мені також сподобалося просте групування панд . це потужний, простий і найбільш адекватний для великого набору даних

result = pandas.DataFrame(input).groupby(1).groups


3

Ця відповідь схожа на відповідь @ PaulMcG, але не потребує сортування введення.

Для тих, хто входить у функціональне програмування, groupByйого можна записати в один рядок (не включаючи імпорт!), І на відміну від itertools.groupbyцього не потрібно сортувати вхідні дані:

from functools import reduce # import needed for python3; builtin in python2
from collections import defaultdict

def groupBy(key, seq):
 return reduce(lambda grp, val: grp[key(val)].append(val) or grp, seq, defaultdict(list))

(Причина ... or grpв lambdaтому , що для цього reduce()на роботу, то lambdaповинен повертати свій перший аргумент, тому що list.append()завжди повертає завжди буде повертати . Тобто це хак , щоб обійти обмеження пітона , що лямбда може оцінювати тільки один вислів.)Noneorgrp

Це повертає дікт, ключі якого знаходять, оцінюючи задану функцію та значення якої - це список вихідних елементів у вихідному порядку. Для прикладу ОП, якщо називати це таким groupBy(lambda pair: pair[1], input), що поверне цей дикт:

{'KAT': [('11013331', 'KAT'), ('9843236', 'KAT')],
 'NOT': [('9085267', 'NOT'), ('11788544', 'NOT')],
 'ETH': [('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH')]}

І відповідно до відповіді @ PaulMcG, запитуваний формат ОП можна знайти, описуючи це у списку. Так це буде робити:

result = {key: [pair[0] for pair in values],
          for key, values in groupBy(lambda pair: pair[1], input).items()}

Набагато менший код, але зрозумілий. Також добре, тому що воно не винаходить колесо.
devdanke

2

Наступна функція швидко ( не потрібно сортувати ) групувати кортежі будь-якої довжини за ключем, що має будь-який індекс:

# given a sequence of tuples like [(3,'c',6),(7,'a',2),(88,'c',4),(45,'a',0)],
# returns a dict grouping tuples by idx-th element - with idx=1 we have:
# if merge is True {'c':(3,6,88,4),     'a':(7,2,45,0)}
# if merge is False {'c':((3,6),(88,4)), 'a':((7,2),(45,0))}
def group_by(seqs,idx=0,merge=True):
    d = dict()
    for seq in seqs:
        k = seq[idx]
        v = d.get(k,tuple()) + (seq[:idx]+seq[idx+1:] if merge else (seq[:idx]+seq[idx+1:],))
        d.update({k:v})
    return d

Що стосується вашого запитання, індекс ключа, за яким потрібно згрупувати, дорівнює 1, отже:

group_by(input,1)

дає

{'ETH': ('5238761','5349618','962142','7795297','7341464','5594916','1550003'),
 'KAT': ('11013331', '9843236'),
 'NOT': ('9085267', '11788544')}

що не є саме тим, про який ви просили, але може відповідати вашим потребам.


Я працюю над програмою MapReduce в python, просто цікаво, чи є якийсь спосіб згрупувати за значеннями в списку, не маючи стосунків зі словниками чи зовнішньою бібліотекою, наприклад, пандами? Якщо ні, то як я можу позбутися предметів і ввести результат?
Курош

0
result = []
# Make a set of your "types":
input_set = set([tpl[1] for tpl in input])
>>> set(['ETH', 'KAT', 'NOT'])
# Iterate over the input_set
for type_ in input_set:
    # a dict to gather things:
    D = {}
    # filter all tuples from your input with the same type as type_
    tuples = filter(lambda tpl: tpl[1] == type_, input)
    # write them in the D:
    D["type"] = type_
    D["itmes"] = [tpl[0] for tpl in tuples]
    # append D to results:
    result.append(D)

result
>>> [{'itmes': ['9085267', '11788544'], 'type': 'NOT'}, {'itmes': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'itmes': ['11013331', '9843236'], 'type': 'KAT'}]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.