2010-09-20 10 views
74

Supongamos que tengo un tal conjunto de datas más par donde el índice 0 es el valor y el índice 1 es del tipo:grupo Python por

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

Quiero agruparlos por su tipo (por la primera indexadas cadena) como tal:

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

¿Cómo puedo lograr esto de una manera eficiente?

Gracias

Respuesta

104

Hazlo en 2 pasos. Primero, crea un diccionario.

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

Luego, convierta ese diccionario al formato esperado.

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

También es posible con itertools.groupby pero requiere la entrada que ser resuelto primero.

>>> 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 ambos éstos no respetan el orden original de las llaves. Necesita un OrderedDict si necesita guardar el pedido.

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

¿Cómo se puede hacer esto si la tupla de entrada tiene una llave y dos o más valores, así: '[('11013331', 'rojo' , 'KAT'), ('9085267', 'azul' 'KAT')] 'donde el último elemento de tupla es la clave y los dos primeros como valor. El resultado debería ser así: resultado = [{ tipo: 'KAT', elementos: [('11013331', rojo), ('9085267', azul)]}] – user1144616

38

de Python incorporada itertools módulo en realidad tiene una función groupby que se puede utilizar, pero los elementos que se agrupan primero deben ser ordenados de tal manera que los elementos a ser agrupados son contiguos en la 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) 

Ahora entrada se ve así:

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

groupby devuelve una secuencia de 2-tuplas, de la forma (key, values_iterator). Lo que queremos es convertir esto en una lista de dicts donde el 'tipo' es la clave, y 'items' es una lista de los elementos 0'th de las tuplas devueltas por el value_iterator. De esta manera:

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

Ahora result contiene su dict deseada, como se indica en su pregunta.

Usted podría considerar, sin embargo, hacer una sola frase de esto, teclear por tipo, y cada valor que contiene la lista de valores. En su forma actual, para encontrar los valores para un tipo particular, tendrá que iterar sobre la lista para encontrar el dictado que contiene la tecla correspondiente 'tipo', y luego obtener el elemento 'elementos' de la misma. Si usa un solo dict en lugar de una lista de dictados de 1 ítem, puede encontrar los ítems para un tipo particular con una sola búsqueda por clave en el dict maestro.Usando groupby, este sería el resultado:

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

result ahora contiene este dict (esto es similar al intermedio res defaultdict en respuesta @ de KennyTM):

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

(Si desea reducir esto a una sola línea, se puede:

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

o el uso de la forma dict-comprensión de última moda:

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

La siguiente función será rápidamente (sin clasificación requeridos) tuplas grupo de cualquier longitud de una llave que tiene cualquier índice:

# 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 

En el caso de su pregunta, el índice de clave que desea al grupo por es 1, por lo tanto:

group_by(input,1) 

da

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

que no es exactamente el resultado que solicitó, pero podría satisfacer sus necesidades.

0

También me gustó pandas simple grouping. Es potente, simple y más adecuado para grandes conjuntos de datos

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