2015-04-29 8 views
6

Ich versuche, durch die effizienteste Möglichkeit, dies in Python zu tun.Reduzieren durch Schlüssel in Python

Angenommen habe ich eine Liste von Tupeln:

[('dog',12,2), ('cat',15,1), ('dog',11,1), ('cat',15,2), ('dog',10,3), ('cat',16,3)] 

Und wenn ich eine Funktion haben, die zwei dieser Tupel nimmt und verbindet sie:

def my_reduce(obj1, obj2): 
    return (obj1[0],max(obj1[1],obj2[1]),min(obj1[2],obj2[2])) 

Wie führe ich ein effizienter reduzieren "Schlüssel", wo der Schlüssel hier der erste Wert sein könnte, so würde das Endergebnis etwas wie sein:

+0

meinten Sie min (obj1 [2], obj2 [2]) – wim

+0

guten Fang, danke! Ich reparierte es über – mgoldwasser

+2

das sieht aus wie etwas gut geeignet für Pandas –

Antwort

5

Wenn Sie Ihren my_reduce und reduce verwenden möchten, können Sie es auf diese Weise tun . Es ist ziemlich kurz, eigentlich:

Zubereitung:

from itertools import groupby 
from operator import itemgetter 

pets = [('dog',12,2), ('cat',15,1), ('dog',11,1), ('cat',15,2), ('dog',10,3), ('cat',16,3)] 

def my_reduce(obj1, obj2): 
    return (obj1[0],max(obj1[1],obj2[1]),min(obj1[2],obj2[2])) 

Lösung:

print [reduce(my_reduce, group) 
     for _, group in groupby(sorted(pets), key=itemgetter(0))] 

Ausgang:

[('cat', 16, 1), ('dog', 12, 1)] 
+0

Darf ich wissen, welche Syntax/Kurzschrift Sie in der print-Anweisung verwendet haben? Es scheint, dass ein Funktionsaufruf gefolgt von einer for-Schleife und einer Variablen, die durch die for-Schleife definiert sind, im Funktionsaufruf übergeben wird. reduce (my_reduce, group) für _, groupby (sortiert (Haustiere), key = itemgetter (0)) – Lee

+0

@Lee Das ist ein "Listenverständnis". –

0

, wenn Sie wirklich reduzieren wollen verwenden Ich denke, das funktioniert (es gibt Ihnen ein dict zurück statt einer Liste, aber meh)

def my_reduce(obj1, obj2): 
    if not isinstance(obj1,dict): 
     return reduce(my_reduce,[{},obj1,obj2]) 
    try: 
     obj1[obj2[0]] = max(obj1[obj2[0]][0],obj2[1]),min(obj1[obj2[0]][1],obj2[2]) 
    except KeyError: 
     obj1[obj2[0]] = obj2[1:] 
    return obj1 

my_list = [('dog',12,2), ('cat',15,1), ('dog',11,1), ('cat',15,2), ('dog',10,3), ('cat',16,3)] 
print reduce(my_reduce,my_list) 

Ich denke, die beiden anderen Lösungen besser sind jedoch

+0

Nein, weil das alles zu einem Element zusammenfallen würde, aber ich möchte ein Element pro Schlüssel – mgoldwasser

+0

ok behoben ... Art von ... –

7

ich nicht denke, reduce ist ein gutes Werkzeug für diesen Job, weil Sie zuerst itertools oder ähnliches verwenden müssen, um die Liste mit dem Schlüssel zu gruppieren. Sonst werden Sie cats und dogs vergleichen und die Hölle wird losbrechen!

Statt nur eine einfache Schleife ist in Ordnung:

>>> my_list = [('dog',12,2), ('cat',15,1), ('dog',11,1), ('cat',15,2)] 
>>> output = {} 
>>> for animal, high, low in my_list: 
...  try: 
...   prev_high, prev_low = output[animal] 
...  except KeyError: 
...   output[animal] = high, low 
...  else: 
...   output[animal] = max(prev_high, high), min(prev_low, low) 

Dann, wenn Sie das ursprüngliche Format wollen zurück:

>>> output = [(k,) + v for k, v in output.items()] 
>>> output 
[('dog', 12, 1), ('cat', 15, 1)] 

Hinweis: dieser die Bestellung aus der ursprünglichen Liste zerstören. Wenn Sie die Reihenfolge beibehalten möchten, in der die Schlüssel zuerst angezeigt werden, initialisieren Sie die Ausgabe stattdessen mit einer OrderedDict.

9

Alternativ, wenn Sie Pandas haben installiert:

import pandas as pd 

l = [('dog',12,2), ('cat',15,1), ('dog',11,1), ('cat',15,2), ('dog',10,3), ('cat',16,3)] 

pd.DataFrame(data=l, columns=['animal', 'm', 'n']).groupby('animal').agg({'m':'max', 'n':'min'}) 
Out[6]: 
     m n 
animal  
cat  16 1 
dog  12 1 

Um das ursprüngliche Format zu erhalten:

zip(df.index, *df.values.T) # df is the result above 
Out[14]: [('cat', 16, 1), ('dog', 12, 1)] 
+2

cool! – wim

+0

ich concure :) .. .silly wim und seine 0-width spaces: P –