Python grubu


125

Dizin 0'ın değer ve dizin 1'in tür olduğu bir veri çiftine sahip olduğumu varsayalım :

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

Onları türlerine göre gruplamak istiyorum (1. dizine göre):

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

Bunu verimli bir şekilde nasıl başarabilirim?

Yanıtlar:


153

2 adımda yapın. Önce bir sözlük oluşturun.

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

Ardından, sözlüğü beklenen biçime dönüştürün.

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

İtertools.groupby ile de mümkündür, ancak önce girdinin sıralanmasını gerektirir.

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

Bunların her ikisinin de anahtarların orijinal sırasına uymadığını unutmayın. Siparişi tutmanız gerekiyorsa, bir OrderedDict'e ihtiyacınız var.

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

Giriş demetinin bir anahtarı ve iki veya daha fazla değeri varsa, bu nasıl yapılabilir: [('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]tuple'ın son öğesi anahtar ve ilk ikisi değer olarak. Sonuç şöyle olmalıdır: sonuç = [{type: 'KAT', items: [('11013331', red), ('9085267', blue)]}]
user1144616

1
from operator import itemgetter
Baumann

1
1. adım içe d= {}; for k,v in input: d.setdefault(k, []).append(v)
aktarmadan

Python'da bir MapReduce programı üzerinde çalışıyorum, sadece sözlüklerle veya pandalar gibi harici kitaplıklarla uğraşmadan bir listedeki değerlere göre gruplandırmanın bir yolu var mı? Değilse, öğelerden nasıl kurtulabilirim ve sonucumu nasıl yazabilirim?
Kourosh

54

Python'un yerleşik itertoolsmodülünün aslında bir groupbyişlevi vardır, ancak bunun için gruplanacak öğelerin, gruplanacak öğeler listede bitişik olacak şekilde önce sıralanması gerekir:

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)

Şimdi giriş şöyle görünür:

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

groupbyformun 2-demet dizisini döndürür (key, values_iterator). İstediğimiz, bunu 'tip'in anahtar olduğu ve' öğeler'in de values_iterator tarafından döndürülen tupleların 0. elemanlarının bir listesi olduğu bir dikt listesine dönüştürmek. Bunun gibi:

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

Şimdi result, sorunuzda belirtildiği gibi istediğiniz dikteyi içerir.

Yine de, bundan, türe göre ve değerler listesini içeren her değere göre anahtarlanmış tek bir dikte yapmayı düşünebilirsiniz. Mevcut formunuzda, belirli bir tür için değerleri bulmak için, eşleşen "tür" anahtarını içeren dikteyi bulmak için listeyi yinelemeniz ve ardından "öğeler" öğesini ondan almanız gerekir. 1 maddelik dikteler listesi yerine tek bir dikt kullanırsanız, belirli bir tür için öğeleri ana diktede tek bir anahtarlı aramayla bulabilirsiniz. Kullanıldığında groupby, bu şöyle görünür:

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

resultşimdi bu resdikteyi içerir (bu, @ KennyTM'nin cevabındaki ara varsayılan dikteye benzer ):

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

(Bunu tek satıra indirmek istiyorsanız, şunları yapabilirsiniz:

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

veya yeni çıkmış dikte-anlama formunu kullanarak:

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

Python'da bir MapReduce programı üzerinde çalışıyorum, sadece sözlüklerle veya pandalar gibi harici kitaplıklarla uğraşmadan bir listedeki değerlere göre gruplandırmanın bir yolu var mı? Değilse, öğelerden nasıl kurtulabilirim ve sonucumu nasıl yazabilirim?
Kourosh

@Kourosh - Yeni bir soru olarak gönderin, ancak "öğelerden kurtulun ve sonucumda yazın" ve "sözlüklerle uğraşmadan" ne demek istediğinizi belirttiğinizden emin olun.
PaulMcG

7

Pandaların basit gruplamasını da sevdim . güçlü, basit ve büyük veri kümesi için en uygun

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


3

Bu cevap @ PaulMcG'nin cevabına benzer ancak girdinin sıralanmasını gerektirmez.

İşlevsel programlamaya girenler için groupBytek satırda yazılabilir (içe aktarmalar dahil değil!) Ve itertools.groupbybunun aksine girdinin sıralanmasını gerektirmez:

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

(Nedeni için ... or grpde lambdabunun için olmasıdır reduce()işe, lambdaihtiyaçlar ilk argüman dönün; çünkü list.append()her zaman döndürür hep dönecektir . Yani bu bir lambda tek bir ifade değerlendirmek yalnızca ki Python en kısıtlamayı aşmanın bir beygir.)Noneorgrp

Bu, anahtarları verilen işlevi değerlendirerek bulunan ve değerleri orijinal sıradaki orijinal öğelerin bir listesi olan bir komut döndürür. OP'nin örneği için, bunu çağırmak groupBy(lambda pair: pair[1], input)bu dikteyi döndürecektir:

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

Ve @ PaulMcG'nin cevabına göre , OP'nin istenen formatı, bunu bir liste anlayışına sararak bulunabilir. Yani bunu yapacak:

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

Çok daha az kod, ancak anlaşılabilir. Tekerleği yeniden icat etmediği için de iyi.
devdanke

2

Aşağıdaki işlev , herhangi bir uzunluktaki demetleri herhangi bir dizine sahip bir anahtarla hızlı bir şekilde gruplandıracaktır ( sıralama gerekmez):

# 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

Sorunuz durumunda, gruplamak istediğiniz anahtar dizini 1'dir, bu nedenle:

group_by(input,1)

verir

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

bu tam olarak istediğiniz çıktı değil, ancak ihtiyaçlarınızı da karşılayabilir.


Python'da bir MapReduce programı üzerinde çalışıyorum, sadece sözlüklerle veya pandalar gibi harici kitaplıklarla uğraşmadan bir listedeki değerlere göre gruplandırmanın bir yolu var mı? Değilse, öğelerden nasıl kurtulabilirim ve sonucumu nasıl yazabilirim?
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'}]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.