Python-Gruppe von


125

Angenommen, ich habe eine Reihe 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 dies effizient erreichen?

Antworten:


153

Mach es in 2 Schritten. Erstellen Sie zunächst 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 das 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'}]

Dies ist auch mit itertools.groupby möglich, erfordert jedoch, dass die Eingabe zuerst sortiert wird.

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

Wie kann dies geschehen, wenn das Eingabetupel einen Schlüssel und zwei oder mehr Werte hat, wie [('11013331', 'red', 'KAT'), ('9085267', 'blue' 'KAT')]folgt : wobei das letzte Element des Tupels der Schlüssel und die ersten beiden als Wert sind. Das Ergebnis sollte folgendermaßen aussehen: result = [{type: 'KAT', items: [('11013331', red), ('9085267', blue)]}]
user1144616

1
from operator import itemgetter
Baumann

1
Schritt 1 kann ohne den Import durchgeführt werden:d= {}; for k,v in input: d.setdefault(k, []).append(v)
Ecoe

Ich arbeite an einem MapReduce-Programm in Python und frage mich nur, ob es eine Möglichkeit gibt, nach Werten in einer Liste zu gruppieren, ohne sich mit Wörterbüchern oder externen Bibliotheken wie Pandas zu befassen. Wenn nicht, wie kann ich dann Elemente entfernen und mein Ergebnis eingeben?
Kourosh

54

Das in Python integrierte itertoolsModul hat tatsächlich eine groupbyFunktion, aber dafür müssen die zu gruppierenden Elemente zuerst 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)

Jetzt 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')]

groupbyGibt eine Folge von 2 Tupeln der Form zurück (key, values_iterator). Was wir wollen, ist, dies in eine Liste von Diktaten umzuwandeln, wobei der 'Typ' der Schlüssel ist und 'Elemente' eine Liste der 0-ten Elemente der Tupel ist, 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)))

resultEnthält jetzt Ihr gewünschtes Diktat, wie in Ihrer Frage angegeben.

Sie könnten jedoch in Betracht ziehen, daraus nur ein einziges Diktat zu machen, das nach Typ und jedem Wert, der die Werteliste enthält, verschlüsselt ist. Um in Ihrem aktuellen Formular die Werte für einen bestimmten Typ zu ermitteln, müssen Sie die Liste durchlaufen, um das Diktat mit dem passenden Schlüssel "Typ" zu finden, und dann das Element "Elemente" daraus abrufen. 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 verschlüsselten Suche im Master-Diktat finden. Mit groupbywürde dies folgendermaßen aussehen:

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

resultenthält jetzt dieses Diktat (dies ähnelt dem Zwischen- resStandarddiktat 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 der neuen Form des Diktverständnisses:

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

Ich arbeite an einem MapReduce-Programm in Python und frage mich nur, ob es eine Möglichkeit gibt, nach Werten in einer Liste zu gruppieren, ohne sich mit Wörterbüchern oder externen Bibliotheken wie Pandas zu befassen. Wenn nicht, wie kann ich dann Elemente entfernen und mein Ergebnis eingeben?
Kourosh

@Kourosh - Als neue Frage posten, aber geben Sie unbedingt an, was Sie unter "Elemente entfernen und mein Ergebnis eingeben" und "ohne Wörterbücher zu behandeln" verstehen.
PaulMcG

7

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

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


3

Diese Antwort ähnelt der Antwort von @ PaulMcG erfordert jedoch kein Sortieren der Eingabe.

Für diejenigen, die sich mit funktionaler Programmierung beschäftigen, groupBykann in einer Zeile geschrieben werden (ohne Importe!), Und im Gegensatz itertools.groupbydazu muss die Eingabe nicht sortiert werden:

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

(Der Grund für die ... or grpin der lambdaist , dass dies für reduce()zu arbeiten, die lambdaBedürfnisse seines erstes Argument zurück, weil list.append()immer wieder Nonedieor wird immer wieder zurückkehren grp. Dh es ein Hack ist Einschränkung zu umgehen , Pythons , dass eine Lambda nur einen einzigen Ausdruck auswerten kann.)

Dies gibt ein Diktat zurück, dessen Schlüssel durch Auswerten der angegebenen Funktion gefunden werden und dessen Werte eine Liste der Originalelemente in der ursprünglichen Reihenfolge sind. Wenn Sie für das Beispiel des OP dies so nennen, groupBy(lambda pair: pair[1], input)wird dieses Dikt zurückgegeben:

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

Und gemäß der Antwort von @ PaulMcG kann das angeforderte Format des OP gefunden werden, indem es in ein Listenverständnis eingeschlossen wird. Das wird es also tun:

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

Viel weniger Code, aber verständlich. Auch gut, weil es das Rad nicht neu erfindet.
Devdanke

2

Die folgende Funktion gruppiert schnell ( keine Sortierung erforderlich) Tupel beliebiger Länge 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 Fall 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, die aber genauso gut Ihren Anforderungen entspricht.


Ich arbeite an einem MapReduce-Programm in Python und frage mich nur, ob es eine Möglichkeit gibt, nach Werten in einer Liste zu gruppieren, ohne sich mit Wörterbüchern oder externen Bibliotheken wie Pandas zu befassen. Wenn nicht, wie kann ich dann Elemente entfernen und mein Ergebnis eingeben?
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'}]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.