2009-11-01 13 views
5

Ich habe eine Liste von Tupeln, die ich versuche zu sortieren und könnte einige Hilfe verwenden.Hilfe Sortierung: zuerst durch diese und dann durch diese

Das Feld, nach dem ich in den Tupeln sortieren möchte, sieht wie "XXX_YYY" aus. Zuerst möchte ich die XXX-Werte in umgekehrter Reihenfolge gruppieren, und dann möchte ich innerhalb dieser Gruppen die YYY-Werte in normaler Sortierreihenfolge platzieren. (Anmerkung: Ich bin gerade so glücklich, tatsächlich, das zweite Element in dem Tupel auf diese Weise sortiert wird, werden in umgekehrter Reihenfolge erstes Wort, normale Reihenfolge zweiten.)

Hier ist ein Beispiel dafür, was ich habe und was Ich möchte am Ende ... nicht sicher, wie es geht.

mylist = [ 
    (u'community_news', u'Community: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_video', u'Community: Video'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_magazine', u'KF: Magazine') 
] 

Ich möchte eine Art von sort() in dieser Liste durchzuführen, die die Ausgabe ändern:

sorted = [ 
    (u'kf_magazine', u'KF: Magazine'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_news', u'Community: News & Information'), 
    (u'community_video', u'Community: Video'), 
] 

Ich vermute, es kann sein, das ein pythonic Weise zu handhaben, aber ich bin nicht in der Lage zu wickeln mein Kopf drumherum.

Antwort

8

Benutzerdefinierte Vergleich Funktionen zum Sortieren, wie in bestehenden Antworten vorgeschlagen, machen es einfach, in einer Mischung von aufsteigend zu sortieren und absteigende Aufträge - aber sie haben schwerwiegende Leistungsprobleme und wurden in Python 3 entfernt, so dass nur die bevorzugte Anpassung Ansatz - benutzerdefinierte Tasten-Extraktion Funktionen ... viel schneller, obwohl zarter für den relativ seltenen Anwendungsfall zu verwenden von gemischt aufsteigende/absteigende Sortierungen.

In Python 2.*, die entweder Art der Anpassung unterstützt (nicht beide im selben Aufruf zu sort oder sorted :-), eine benutzerdefinierten Vergleichsfunktion kann als cmp= benannte Argument übergeben werden; oder eine benutzerdefinierte Schlüsselextraktionsfunktion kann als key= genanntes Argument übergeben werden. In Python 3.* ist nur die letztere Option verfügbar.

Es ist definitiv wert, den Schlüsselextraktionsansatz zu verstehen, auch wenn Sie glauben, dass Sie Ihr Problem gerade mit einem benutzerdefinierten Vergleichsansatz gelöst haben: nicht nur für die Leistung, sondern auch für die Zukunftssicherheit (Python 3) und für die Allgemeinheit (Der key= Ansatz gilt auch für min, max, itertools.groupby ... viel allgemeiner als der cmp= Ansatz!).

Die Schlüsselextraktion ist sehr einfach, wenn alle Schlüsselunterfelder auf die gleiche Weise sortiert werden sollen (alle aufsteigend oder alle absteigend) - Sie extrahieren sie einfach; es ist immer noch ziemlich einfach, wenn die Teilfelder, die "andersherum" gehen, Zahlen sind (man ändert nur ihr Vorzeichen während des Extrahierens); Der heikle Fall ist genau der Fall, den Sie haben - mehrere String-Felder, die auf unterschiedliche Weise verglichen werden müssen.

Ein relativ einfacher Ansatz, Ihr Problem zu lösen, ist eine winzige Shim Klasse:

class Reverser(object): 
    def __init__(self, s): self.s = s 
    def __lt__(self, other): return other.s < self.s 
    def __eq__(self, other): return other.s == self.s 

Beachten Sie, dass Sie nur __lt__ und __eq__ (die < und == Betreiber) zu versorgen haben - sort und Freunde synthetisieren all andere Vergleiche, falls erforderlich, basierend auf diesen beiden.

Also, mit diesem kleinen Hilfsmittel bewaffnet, wir leicht gehen können ...:

def getkey(tup): 
    a, b = tup[0].split('_') 
    return Reverser(a), b 

my_list.sort(key=getkey) 

Wie Sie sehen, wenn Sie „get“ die Umkehrer und Schlüssel Absaugkonzepte, zahlen Sie im Wesentlichen kein Preis für Verwenden von Schlüssel - Extraktion anstelle von benutzerdefinierten Vergleich: der Code, den ich vorschlage, ist 4 Anweisungen für die Umkehrer - Klasse (die Sie einmal schreiben können und irgendwo in Ihrem "Goodies Bag" Modul), drei für die Schlüsselextraktion Funktion, und natürlich eine für die sort oder sorted Aufruf - insgesamt acht vs die 4 + 1 == 5 des benutzerdefinierten Vergleichs Ansatz in der kompaktesten Form (dh die entweder cmp mit einem Vorzeichenwechsel oder cmp mit vertauschten argume nts). Drei Aussagen sind nicht viel von einem Preis für die Vorteile der Schlüsselextraktion! -)

Leistung ist eindeutig kein großes Problem mit solch einer kurzen Liste, aber mit einem sogar bescheidenen (10 mal) eins ...:

# my_list as in the Q, my_cmp as per top A, getkey as here 

def bycmp(): 
    return sorted(my_list*10, cmp=my_cmp) 

def bykey(): 
    return sorted(my_list*10, key=getkey) 

... 

$ python -mtimeit -s'import so' 'so.bykey()' 
1000 loops, best of 3: 548 usec per loop 
$ python -mtimeit -s'import so' 'so.bycmp()' 
1000 loops, best of 3: 995 usec per loop 

Ie, der key= Ansatz bereits eine Leistungssteigerung von fast zwei mal zeigt (die Liste doppelt so schnell Sortierung), wenn sie auf einer 50-Elemente-Liste arbeiten - auch den bescheidenen Preis von „8 Zeilen wert eher als 5 ", besonders mit all den anderen Vorteilen, die ich bereits erwähnt habe!

+0

Wow, ich mag deine Lösung. Ich wusste nicht, dass der cmp = Ansatz eine solche Strafe hatte. –

+0

@Steven, tx - yep, nicht jeder versteht, warum cmp = in Python 3 entfernt wurde (als "attraktives Ärgernis", das Leute dazu verleitet, eine Leistungseinbuße zu erleiden!), Genau deshalb habe ich diese detaillierte Erklärung gepostet, danke zur Bestätigung kann es helfen! -) –

+2

@Alex: Ich zögere, eine * Antwort * zu bearbeiten, aber vielleicht sollte my_list.key (cmp = my_cmp) my_list.sort (key = getkey) sein? –

10
def my_cmp(x, y): 
    x1, x2 = x[0].split('_') 
    y1, y2 = y[0].split('_') 
    return -cmp(x1, y1) or cmp(x2, y2) 

my_list = [ 
    (u'community_news', u'Community: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_video', u'Community: Video'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_magazine', u'KF: Magazine') 
] 

sorted_list = [ 
    (u'kf_magazine', u'KF: Magazine'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_news', u'Community: News & Information'), 
    (u'community_video', u'Community: Video'), 
] 

my_list.sort(cmp=my_cmp) 
assert my_list == sorted_list 
+1

Ich wollte gerade meine bearbeiten, um stattdessen den CMP-Anruf zu negieren, wenn Sie Ihre Antwort gepostet haben. :) – Kylotan

+1

Ich habe den Vergleich zu '-cmp (x1, y1) oder cmp (x2, y2)' vereinfacht. :) –

+0

Sie könnten auch das Schlüsselargument übergeben, um den Split am Anfang Ihrer Funktion zu sortieren und loszuwerden: my_list.sort (cmp = my_cmp, key = Lambda x: x [0] .split ('_')) –

2
>>> def my_cmp(tuple_1, tuple_2): 
    xxx_1, yyy_1 = tuple_1[0].split('_') 
    xxx_2, yyy_2 = tuple_2[0].split('_') 
    if xxx_1 > xxx_2: 
     return -1 
    elif xxx_1 < xxx_2: 
     return 1 
    else: 
     return cmp(yyy_1, yyy_2) 


>>> import pprint 
>>> pprint.pprint(sorted(mylist, my_cmp)) 
[(u'kf_magazine', u'KF: Magazine'), 
(u'kf_news', u'KF: News & Information'), 
(u'kf_video', u'KF: Video'), 
(u'community_news', u'Community: News & Information'), 
(u'community_video', u'Community: Video')] 

nicht die schönste Lösung in der Welt ...

Verwandte Themen