Seite 1 von 2

kompliziertes Sortieren mehrerer Listen

Verfasst: Dienstag 21. Oktober 2008, 19:48
von Mandos
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?

Verfasst: Dienstag 21. Oktober 2008, 20:56
von acoolon

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

Verfasst: Dienstag 21. Oktober 2008, 21:16
von Mandos
Vielen Dank! Ich versteh zwar nicht alles, aber es funktioniert ganz gut.
Wie kann ich das jetzt mit dem parallelen sortieren Verknüpfen?

Verfasst: Dienstag 21. Oktober 2008, 21:36
von acoolon

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

;)

Verfasst: Dienstag 21. Oktober 2008, 22:16
von numerix
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.ä.

Verfasst: Dienstag 21. Oktober 2008, 22:31
von acoolon
:roll:

deswegen sagte ich ja auch murks...da es soweit hingebogen wurde, bis es fuer den Fall lief ;)

Verfasst: Dienstag 21. Oktober 2008, 23:00
von Leonidas
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

Verfasst: Dienstag 21. Oktober 2008, 23:39
von Mandos
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.

Verfasst: Dienstag 21. Oktober 2008, 23:41
von snafu
Ein guter Anfang wäre es vielleicht, die zweite Liste mal zu zeigen. ;)

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

Verfasst: Mittwoch 22. Oktober 2008, 08:25
von 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.

Verfasst: Mittwoch 22. Oktober 2008, 08:30
von sma

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

Verfasst: Mittwoch 22. Oktober 2008, 08:35
von Leonidas
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?

Verfasst: Mittwoch 22. Oktober 2008, 08:38
von 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.

Verfasst: Mittwoch 22. Oktober 2008, 08:50
von sma
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

Verfasst: Mittwoch 22. Oktober 2008, 08:56
von Leonidas
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 ;)

Verfasst: Mittwoch 22. Oktober 2008, 11:11
von Mandos
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.

Verfasst: Mittwoch 22. Oktober 2008, 11:39
von 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.

Verfasst: Mittwoch 22. Oktober 2008, 11:46
von Mandos
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ß.

Verfasst: Mittwoch 22. Oktober 2008, 12:02
von numerix
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

Verfasst: Mittwoch 22. Oktober 2008, 16:29
von Mandos
Vielen, vielen Dank!! Das funktioniert perfekt.