kompliziertes Sortieren mehrerer Listen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Mandos
User
Beiträge: 6
Registriert: Dienstag 21. Oktober 2008, 19:25

Hallo,
ich bin nicht so der python-crack und bei etwas komplizierterem Sortieren geht mir die Fantasie aus.
Folgendes Problem: Ich hab eine Liste, die z.B. so aussehen kann:

['2,17', '20,6','8,33-34.', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5']

Diese Liste möchte ich gerne sortieren und zwar erst nach der Zahl vor dem Komma und dann nach der Zahl nach dem Komma...das würde in diesem Beispiel dann so aussehen:

['1,42', '2,10', '2,13', '2,17', '5', '8,33', '8,33-34', '8,33-35', '8,37-59', '8,44.45', '20,6']

Dadurch, dass das strings sind ist die list.sort()-Funktion etwas blöd, weil er dann sowas produziert:
['1,42', '2,10', '2,13', '2,17', '20,6', '5', '8,33', '8,33-34', '8,33-35', '8,37-59', '8,44.45']

Da ist dann '20,6' nicht am Ende sondern in der Mitte, weil er halt nicht die Zahlen erkennt und nur ein Zeichen nach dem anderen vergleicht. Nun hatte ich versucht das in zwei Listen zu teilen - eine mit dem ersten Teil (vor dem Komma) und die andere mit dem zweiten Teil enthalten, aber weiter wusste ich dann auch nicht wirklich und in int kann man das mit den Bindestrichen und Punkten ja auch nicht umwandeln.
Dazu kommt noch, dass ich eine zweite Liste mit Zahlen habe, die der ersten List zugeordnet sind. Wenn also die erste (komplexere) Liste umgeordnet wird, dann müsste die zweite parallel umgeordnet werden...
Im Forum bin ich auf das hier gestoßen:

Code: Alles auswählen

# Paralleles Sortieren von zwei Listen
# Autor: Dieter Schön
# Quelle: http://www.xing.com/app/forum?op=showarticles;id=2003500

a = ['3','2','4','1']
b = ['uschi','peter','karsten','jens']

def parallel_sort_zipzip(list1, list2):
    tuples = zip(list1, list2)
    tuples.sort()
    return zip(*tuples)
   
a,b = parallel_sort_zipzip(a, b)
Das ist schon ganz hilfreich, aber das Problem mit den strings bleibt noch.
Viele Worte, aber ich glaube so schwer ist das gar nicht, wenn man nur weiß wies geht ;)
Weiß es hier jemand?
acoolon
User
Beiträge: 27
Registriert: Samstag 2. August 2008, 20:16

Code: Alles auswählen

a = ['2,17', '20,6','8,33-34.', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5'] 
b = [ i.split(',') for i in a]
b = [ [int(i[0]),i[-1]] for i in b]
b.sort()
c = [ [i[0],b.index(i)] for i in b if str(i[0]) == i[-1]]
a = [str(i[0])+','+i[-1] for i in b]
if c:
	for i in c:
		a[i[-1]] = i[0]
print a
das ist murks ohne Ende, aber es bringt bei mir das Ergebnis welches du willst... :)


mfg acoolon
Mandos
User
Beiträge: 6
Registriert: Dienstag 21. Oktober 2008, 19:25

Vielen Dank! Ich versteh zwar nicht alles, aber es funktioniert ganz gut.
Wie kann ich das jetzt mit dem parallelen sortieren Verknüpfen?
acoolon
User
Beiträge: 27
Registriert: Samstag 2. August 2008, 20:16

Code: Alles auswählen

def parallel_sort_zipzip(list1, list2):
    tuples = zip(list1, list2)
    tuples.sort()
    return zip(*tuples)

x = [1,2,3,4,5,6,7,8,9,10,11]
a =['2,17', '20,6','8,33-34.', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5'] 

b = [ i.split(',') for i in a]
b = [ [int(i[0]),i[-1]] for i in b]
b,x = parallel_sort_zipzip(b, x)
b = list(b)
c = [ [i[0],b.index(i)] for i in b if str(i[0]) == i[-1]]
a = [str(i[0])+','+i[-1] for i in b]
if c:
	for i in c:
		a[i[-1]] = i[0]

print a,x
Wir nehmen an, dass a die komplexe Liste und x die einfache Liste ist.

Code: Alles auswählen

b,x = parallel_sort_zipzip(b, x)
b = list(b)
sollte laufen...

;)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

acoolon hat geschrieben:das ist murks ohne Ende, aber es bringt bei mir das Ergebnis welches du willst... :)
Das ist nicht nur Murks, sondern funktioniert auch nicht richtig.
Zum einen ist die '5' in deiner Ergebnisliste ein Integerwert, zum anderen wird nicht richtig sortiert, wenn einstellige Angaben nach dem Komma folgen. Ersetze mal "2,17" durch "2,8" ...

Eine mögliche Lösung könnte so aussehen:

Code: Alles auswählen

def stellenwert(s):
    s = s+",1" if not "," in s else s.replace("-",",").replace(".",",")
    return "".join([v.zfill(3) for v in s.split(",")])

stellen = ['2,8', '20,6','8,33-34', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5']
stellen.sort(key=stellenwert)
print stellen
Liefert:

Code: Alles auswählen

['1,42', '2,8', '2,10', '2,13', '5', '8,33', '8,33-34', '8,33-35', '8,37-59', '8,44.45', '20,6']
Sollte meine Vermutung richtig sein und es sich bei den Daten um Bibelstellenangaben handeln, dann funktioniert meine Variante auch mit weiteren möglichen Variationen, wie z.B. "8,21-31.34.41" u.ä.
acoolon
User
Beiträge: 27
Registriert: Samstag 2. August 2008, 20:16

:roll:

deswegen sagte ich ja auch murks...da es soweit hingebogen wurde, bis es fuer den Fall lief ;)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Also so kompliziert ists nun auch nicht.

Code: Alles auswählen

import re
l = ['2,17', '20,6','8,33-34.', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5']
numbers = re.compile(r'\d+')
divided = [(map(int, numbers.findall(e)), index) for index, e in enumerate(l)]
divided.sort()
new_l = [l[index] for head, index in divided]
print new_l
In Zeile 5 werden alle Zahlen aus dem Listeneintrag geholt und zu richtigen Integern gemacht. Diese werden zusammen mit dem ursprünglichen Listeneintrag in einer temporären Liste abgespeichert. Diese wird dann sortiert und da wir nun Integer haben auch richtigherum. Dann wird die nun sortierte Liste durchgegangen und wir holen das Ursprüngliche Listenelement über den Index den wir uns gemerkt haben zurück, diesmal aber mit der richtigen Sortierung.

Man kann divided auch etwas zusammenfassen in dem man dort statt LCs GEs verwendet und statt dem expliziten sortieren das ``sorted()`` builtin:

Code: Alles auswählen

import re
l = ['2,17', '20,6','8,33-34.', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5']
numbers = re.compile(r'\d+')
divided = sorted((map(int, numbers.findall(e)), index) for index, e in enumerate(l))
new_l = [l[index] for head, index in divided]
print new_l
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Mandos
User
Beiträge: 6
Registriert: Dienstag 21. Oktober 2008, 19:25

numerix hat geschrieben: Sollte meine Vermutung richtig sein und es sich bei den Daten um Bibelstellenangaben handeln, dann funktioniert meine Variante auch mit weiteren möglichen Variationen, wie z.B. "8,21-31.34.41" u.ä.
Ja, deine Vermutung stimmt. ;)
Wie kann ich dann jetzt die zweite Liste parallel zur ersten ordnen?

Danke für eure Mühe.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein guter Anfang wäre es vielleicht, die zweite Liste mal zu zeigen. ;)

Aus dem Bauch heraus würde ich ein Dictionary empfehlen.
BlackJack

@Mandos: Mit `zip()` die zusammengehörigen Elemente beider Listen in Tupel packen, die Schlüsselfunktion so abändern, dass sie auf das Element mit der Bibelstelle zugreift, und dann eventuell die Tupel wieder auseinander nehmen und auf zwei Listen aufteilen.

Das das alles ein wenig kompliziert aussieht, liegt vielleicht auch daran, dass die Bibelstelle als Zeichenkette schlecht zu verarbeiten ist und das es keine so gute Idee ist, zusammengehörige Information in "parallelen" Listen zu speichern.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Code: Alles auswählen

a = ['2,17', '20,6','8,33-34.', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5'] 
b = range(len(a))
c = zip(a, b)
c.sort(key=lambda o: int(o[0].split(",")[0]))
print c
Stefan
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Hat das ``range()`` und ``zip()`` irgendeinen Unterschied gegenüber ``enumerate()``? Jetzt mal von der Position vom Index mal abgesehen, der in dem Fall sowieso nicht so relevant ist?
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

@sma: Das berücksichtigt jetzt aber nur die Zahl vor dem Komma.

@Leonidas: `b` sollen Dummydaten sein, die die zweite, parallel zu sortierende Liste darstellen. Ich vermute mal in "echt" lässt sich das nicht einfach durch `enumerate()` ersetzen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

BlackJack hat geschrieben:@sma: Das berücksichtigt jetzt aber nur die Zahl vor dem Komma.
so hatte ich die Aufgabe interpretiert. Der zweite Teil war zum Teil ja gar keine Zahl oder fehlte.

Wenn man den zweiten Teil auch haben will, könnte sowas funktionieren:

Code: Alles auswählen

def keyfunc(o):
    a = o[0].split(",")
    return int(a[0]), a[1] if len(a) > 1 else ''
c.sort(key=keyfunc)
BlackJack hat geschrieben:@Leonidas: `b` sollen Dummydaten sein, die die zweite, parallel zu sortierende Liste darstellen. Ich vermute mal in "echt" lässt sich das nicht einfach durch `enumerate()` ersetzen.
Genau.

Stefan
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

BlackJack hat geschrieben:`b` sollen Dummydaten sein, die die zweite, parallel zu sortierende Liste darstellen. Ich vermute mal in "echt" lässt sich das nicht einfach durch `enumerate()` ersetzen.
Ah, dadurch macht es wesentlich mehr Sinn. Ich für meinen Teil wäre zu faul eine Dummy-Liste zu machen, wenn der OP die Liste nicht von selbst verrät ;)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Mandos
User
Beiträge: 6
Registriert: Dienstag 21. Oktober 2008, 19:25

snafu hat geschrieben:Ein guter Anfang wäre es vielleicht, die zweite Liste mal zu zeigen. ;)

Aus dem Bauch heraus würde ich ein Dictionary empfehlen.
Die zweite Liste würde dann z.B. so aussehen:
['44,53', '35', '25', '46', '10', '27', '76', '48,67', '25', '18', '14']
Es handelt sich hierbei um Seitenzahlen. Was ich machen will ist einen Index für Latex zu machen, indem die zitierten Bibelstellen in der Reihenfolge der Bücher der Bibel und dann halt nach Kapitel und Vers geordnet mit zugehörigen Seitenzahlen aufgelistet werden. Bis auf das Sortieren nach Kapitel und Vers funktioniert auch alles.
BlackJack

Was bedeutet denn dann '44,53' als Seitanzahl(en)? Seite 44 bis 53? Würde man das nicht eher als '44-53' schreiben?

Wie schon gesagt, "parallele" Listen sind in der Regel keine gute Idee. Man könnte die Daten in ein Dictionary stecken das Bibelstellen den Zahlen zuordnet und dann `dict.items()` mit einer entsprechenden `key`-Funktion sortieren.

Oder man implementiert eine `Bibelstelle`-Klasse, die alle Angaben, inklusive der Seitenzahl(en) enthält und `__cmp__()` entsprechend implementiert, so dass man die Objekte sortieren kann.
Mandos
User
Beiträge: 6
Registriert: Dienstag 21. Oktober 2008, 19:25

44,53 bedeutet Seite 44 und Seite 53.

Also wenn das mit dem dictionary oder der klasse besser ist...wie aufwändig ist es, sich das jetzt anzueignen? Ich wollte eigentlich nur diesen Index machen und jetzt hab ich schon einige Stunden da dran verbracht und wurde von einem LaTex-forum auf Python verwiesen. Wie gesagt, meine Pythonkenntnisse sind nicht so groß.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

BlackJack hat geschrieben:Was bedeutet denn dann '44,53' als Seitanzahl(en)? Seite 44 bis 53? Würde man das nicht eher als '44-53' schreiben?
Ich deute das ganze so: Mandos schreibt eine Art Haus-/Examens- o.ä. Arbeit, die Haufenweise Bibelstellen enthält, und zwar z.T. dieselbe Stelle auf verschiedenen Seiten seiner Arbeit.

Jetzt will er im Anhang eine Liste der verwendeten Bibelstellen sortiert nach biblischer Reihenfolge und dahinter jeweils alle Seitenzahlen, wo die Stelle verwendet wurde.

@Mandos:
Du brauchst nur die beiden Listen mit zip() zu Tupeln zusammenzusetzen, den sort-key aus meinem Posting etwas abzuändern und fertig:

Code: Alles auswählen

def eintragswert(eintrag):
    s = eintrag[0]
    s = s+",1" if not "," in s else s.replace("-",",").replace(".",",")
    return "".join([v.zfill(3) for v in s.split(",")])

stellen = ['2,8', '20,6','8,33-34', '8,33', '8,37-59', '1,42','8,33-35','8,44.45', '2,10', '2,13','5']
seiten = ['44,53', '35', '25', '46', '10', '27', '76', '48,67', '25', '18', '14']
eintraege = zip(stellen,seiten)
eintraege.sort(key=eintragswert)
for eintrag in eintraege:
    print "%8s: Seite %s" %(eintrag)
Liefert:

Code: Alles auswählen

    1,42: Seite 27
     2,8: Seite 44,53
    2,10: Seite 25
    2,13: Seite 18
       5: Seite 14
    8,33: Seite 46
 8,33-34: Seite 25
 8,33-35: Seite 76
 8,37-59: Seite 10
 8,44.45: Seite 48,67
    20,6: Seite 35
Mandos
User
Beiträge: 6
Registriert: Dienstag 21. Oktober 2008, 19:25

Vielen, vielen Dank!! Das funktioniert perfekt.
Antworten