Gruppo Python di


125

Supponiamo di avere una serie di coppie di dati in cui l' indice 0 è il valore e l' indice 1 è il tipo:

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

Voglio raggrupparli per tipo (per la prima stringa indicizzata) come tali:

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

Come posso ottenerlo in modo efficiente?

Risposte:


153

Fallo in 2 passaggi. Per prima cosa, crea un dizionario.

>>> 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)
...

Quindi, converti quel dizionario nel formato previsto.

>>> [{'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'}]

È anche possibile con itertools.groupby ma richiede che l'input venga ordinato prima.

>>> 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'}]

Nota che entrambi non rispettano l'ordine originale delle chiavi. Hai bisogno di un OrderedDict se devi mantenere l'ordine.

>>> 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'}]

Come può essere fatto se la tupla di input ha una chiave e due o più valori, in questo modo: [('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]dove l'ultimo elemento della tupla è chiave e i primi due come valore. Il risultato dovrebbe essere questo: risultato = [{tipo: 'KAT', elementi: [('11013331', rosso), ('9085267', blu)]}]
user1144616

1
from operator import itemgetter
Baumann

1
il passaggio 1 può essere eseguito senza l'importazione:d= {}; for k,v in input: d.setdefault(k, []).append(v)
ecoe

Sto lavorando a un programma MapReduce in python, mi chiedo solo che esiste un modo per raggruppare i valori in un elenco senza occuparsi di dizionari o librerie esterne come i panda? In caso contrario, come posso eliminare gli elementi e digitare il risultato?
Kourosh

54

Il itertoolsmodulo integrato di Python ha effettivamente una groupbyfunzione, ma per questo gli elementi da raggruppare devono prima essere ordinati in modo tale che gli elementi da raggruppare siano contigui nell'elenco:

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)

Ora l'input ha questo aspetto:

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

groupbyrestituisce una sequenza di 2-tuple, della forma (key, values_iterator). Quello che vogliamo è trasformare questo in un elenco di dict in cui il "tipo" è la chiave e "elementi" è un elenco degli elementi 0 delle tuple restituite da values_iterator. Come questo:

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)))

Ora resultcontiene il dict desiderato, come indicato nella tua domanda.

Potresti considerare, tuttavia, di creare un singolo dict da questo, con chiave per tipo e ogni valore contenente l'elenco di valori. Nella tua forma attuale, per trovare i valori per un particolare tipo, dovrai scorrere l'elenco per trovare il dict contenente la chiave "type" corrispondente, e quindi ottenere l'elemento "items" da esso. Se usi un singolo dict invece di un elenco di dict a 1 elemento, puoi trovare gli elementi per un particolare tipo con una singola ricerca con chiave nel dict principale. Utilizzando groupby, questo sarebbe simile a:

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

resultora contiene questo dict (questo è simile al resdefaultdict intermedio nella risposta di @ KennyTM):

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

(Se vuoi ridurlo a una battuta, puoi:

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

o usando il nuovo modulo di comprensione dei dettami:

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

Sto lavorando a un programma MapReduce in python, mi chiedo solo che esiste un modo per raggruppare i valori in un elenco senza occuparsi di dizionari o librerie esterne come i panda? In caso contrario, come posso eliminare gli elementi e digitare il risultato?
Kourosh

@Kourosh - Pubblica come nuova domanda, ma assicurati di indicare cosa intendi per "sbarazzati degli elementi e digita il mio risultato" e "senza occuparti dei dizionari".
PaulMcG

7

Mi piaceva anche il semplice raggruppamento dei panda . è potente, semplice e più adatto per grandi set di dati

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


3

Questa risposta è simile alla risposta di @ PaulMcG ma non richiede l'ordinamento dell'input.

Per coloro che si dedicano alla programmazione funzionale, groupBypuò essere scritto in una riga (escluse le importazioni!), E diversamente itertools.groupbynon richiede l'ordinamento dell'input:

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))

(Il motivo ... or grpin lambdaè che per questo reduce()al lavoro, le lambdadeve restituire il suo primo argomento, perché list.append()restituisce sempre Nonela orsarà sempre tornare grp. Vale a dire che è un trucco per aggirare la restrizione di pitone che un lambda solo può valutare una singola espressione.)

Questo restituisce un dict le cui chiavi vengono trovate valutando la funzione data ei cui valori sono un elenco degli elementi originali nell'ordine originale. Per l'esempio dell'OP, chiamare questo come groupBy(lambda pair: pair[1], input)restituirà questo dict:

{'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')]}

E secondo la risposta di @ PaulMcG, il formato richiesto dell'OP può essere trovato racchiudendolo in una lista di comprensione. Quindi questo lo farà:

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

Molto meno codice, ma comprensibile. Buono anche perché non reinventa la ruota.
devdanke

2

La seguente funzione raggrupperà rapidamente ( non è richiesto alcun ordinamento ) tuple di qualsiasi lunghezza mediante una chiave con un indice:

# 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

Nel caso della tua domanda, l'indice della chiave che vuoi raggruppare è 1, quindi:

group_by(input,1)

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

che non è esattamente l'output che hai richiesto, ma potrebbe anche soddisfare le tue esigenze.


Sto lavorando a un programma MapReduce in python, mi chiedo solo che esiste un modo per raggruppare i valori in un elenco senza occuparsi di dizionari o librerie esterne come i panda? In caso contrario, come posso eliminare gli elementi e digitare il risultato?
Kourosh

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'}]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.