webentwicklung-frage-antwort-db.com.de

Python-Gruppe von

Angenommen, ich habe ein Paar von Datenpaaren, wobei index 0 der Wert und index 1 der Typ ist:

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

Ich möchte sie nach ihrem Typ (nach der ersten indizierten Zeichenfolge) als solche gruppieren:

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

Wie kann ich das effizient erreichen?

97
Hellnar

Mach es in 2 Schritten. Erstellen Sie zuerst ein Wörterbuch.

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

Konvertieren Sie dann dieses Wörterbuch in das erwartete Format.

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

Es ist auch mit itertools.groupby möglich, es muss jedoch die Eingabe zuerst sortiert werden.

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

Beachten Sie, dass beide die ursprüngliche Reihenfolge der Schlüssel nicht berücksichtigen. Sie benötigen ein OrderedDict, wenn Sie die Bestellung beibehalten möchten.

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

Das in Python integrierte itertools-Modul hat tatsächlich eine groupby - Funktion, aber dazu müssen die zu gruppierenden Elemente zunächst so sortiert werden, dass die zu gruppierenden Elemente in der Liste zusammenhängend sind:

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)

Nun sieht die Eingabe so aus:

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

groupby gibt eine Folge von 2-Tupeln der Form (key, values_iterator) zurück. Was wir wollen, ist eine Liste von Diktaten, bei denen der 'Typ' der Schlüssel ist und 'Elemente' eine Liste der 0'-Elemente der Tupel, die vom values_iterator zurückgegeben werden. So was:

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

Nun enthält result Ihr gewünschtes Diktat, wie in Ihrer Frage angegeben.

Sie könnten jedoch in Betracht ziehen, nur ein einziges Diktat zu erstellen, und zwar nach Typ und nach jedem Wert, der die Liste der Werte enthält. Um in Ihrem aktuellen Formular die Werte für einen bestimmten Typ zu finden, müssen Sie die Liste durchlaufen, um das Diktat zu finden, das den passenden 'type'-Schlüssel enthält, und dann das' items'-Element daraus abzurufen. Wenn Sie ein einzelnes Diktat anstelle einer Liste von 1-Element-Diktaten verwenden, können Sie die Elemente für einen bestimmten Typ mit einer einzigen Tastensuche im Hauptdiktat suchen. Mit groupby würde dies folgendermaßen aussehen:

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

result enthält jetzt dieses Diktat (dies ähnelt dem Zwischen-res-Defaultdict in der Antwort von @ KennyTM):

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

(Wenn Sie dies auf einen Einzeiler reduzieren möchten, können Sie:

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

oder unter Verwendung des neuartigen Diktierverstehens:

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

Ich mochte auch pandas simple grouping . Es ist leistungsstark, einfach und am besten für große Datenmengen geeignet

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

2
akiva

Die folgende Funktion gruppiert Tupel beliebiger Länge schnell ( keine Sortierung erforderlich) nach einem Schlüssel mit einem Index:

# 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

Im Falle Ihrer Frage ist der Index des Schlüssels, nach dem Sie gruppieren möchten, 1, daher:

group_by(input,1)

gibt

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

dies ist nicht genau die Ausgabe, nach der Sie gefragt haben, kann aber auch Ihren Bedürfnissen entsprechen.

1
mmj
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'}]
0
amir-t