2010-09-20 10 views
74

Si supponga che ho un tale insieme di coppia dati dove l'indice 0 è il valore e l'indice 1 è del tipo:gruppo Python

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 il loro tipo (dal 1 ° indicizzato stringa) in quanto tale:

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

come posso raggiungere questo obiettivo in modo efficiente?

Grazie

risposta

104

farlo in 2 passi. 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, convertire 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'}] 

È altresì possibile con itertools.groupby ma richiede l'ingresso da filtrate 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 entrambe queste non rispettano l'ordine originale delle chiavi. Hai bisogno di un OrderedDict se hai bisogno di 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'}] 
+0

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

38

di Python modulo incorporato itertools in realtà ha una funzione groupby che si potrebbe usare, ma gli elementi per essere raggruppati devono prima essere ordinati in modo tale che gli elementi che devono essere raggruppati sono contigui nella lista:

sortkeyfn = key=lambda s:s[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 d'ingresso si presenta come:

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

groupby restituisce una sequenza di 2-tuple, della forma (key, values_iterator). Quello che vogliamo è trasformarlo in una lista di dict in cui il 'tipo' è la chiave, e 'items' è una lista degli 0'th elementi 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 result contiene tua dict desiderato, come affermato nella sua interrogazione.

Si potrebbe prendere in considerazione, tuttavia, solo facendo un singolo dettato da questo, digitato per tipo, e ogni valore contenente l'elenco dei 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 si utilizza un singolo dict invece di un elenco di dict 1 elemento, è possibile trovare gli elementi per un determinato tipo con una singola ricerca con chiave nel dettato principale.Utilizzando groupby, questo sarebbe simile:

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

result ora contiene questa dict (questo è simile a quello intermedio res defaultdict in @ di KennyTM risposta):

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

(Se si vuole ridurre questo per una battuta, è possibile:

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

oppure utilizzando il modulo dict-comprensione diavolerie:

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

La seguente funzione rapidamente (No Sorting richiesto) tuple di gruppo di qualsiasi lunghezza di 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 di chiave vuoi al gruppo da 1 è, pertanto:

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.

0

Mi piacevano anche i panda semplici grouping. è potente, semplice e più adeguata per grandi set di dati

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

Problemi correlati