2 Listen parallel sortieren

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.
Antworten
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Hallo,

ist es möglich 2 Tabellen parallel zu sortieren ? D.h. ich sortiere eine 2 dimensionale Tabelle nach einer bestimmten Spalte und eine zweite eindimensionale Tabelle sollte parallel mitsortieret werden.

Code: Alles auswählen

Pseudolisten:
Liste 1                            Liste 2
[[Huber, Dorfstraße, VW],           [1,
 [Meier, Waldstraße, Opel],           2,
 [Karl, Musterstraße, Ford],          3,
 [Merkel, Schloßallee, Audi]]         4]

Liste 2 sollte nach dem Sortieren (Spalte 1) so aussehen:

Liste 1                            Liste 2
[[Huber, Dorfstraße, VW],           [1,
 [Karl, Musterstraße, Ford],          3,
 [Merkel, Schloßallee, Audi],         4,
 [Meier, Waldstraße, Opel]]           2]
Zum Sortieren der Liste 1 verwende ich diese Methode:

Code: Alles auswählen

def sort_by_column(self, SPALTE=0):  
    self.tabelle = deepcopy(sorted(self.tabelle, key=lambda x: x[SPALTE]))
Ich fand bisher nur Beispiele in denen zwei eindimensionale Listen (mit zip) parallel sortiert wurden.


P.S. Kleine Nebenfrage:
Konstanten sollen durchgehend groß geschrieben werden: Demnach habe ich im Hauptprogramm Konstanten a´la
TITEL = 10
AUTOR = 11
Diese werden dann zum Sortieren beim Methodenaufruf mitgegeben.

Code: Alles auswählen

tabelle.sort_by_column(TITEL)
In der Methode darf dieses 'SPALTE' natürlich auch nicht verändert werden, insofern würde die Großschreibung auch hier passen....
Aber, der Aufruf erfolgt ja mit verschiedenen Werten für SPALTE, also ändert sich deren Wert (zumindest vor dem Aufruf der Methode schon immer wieder)
Ist es nun richtig, im Methodenkopf 'SPALTE' groß zu schreiben, oder wäre 'spalte' richtig ?
(Ich weiß, das mag mehr wie eine akademische Frage klingen, aber die Tabellenklasse ist fast fertig und nun versuche ich halt den Code soweit wie möglich nach PEP 8 auszurichten ;)

MfG
Thomas
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Das riecht mir schon mal nach einem Design-Fehler, wenn du 2 so stark verwandte Daten in getrennten Datenstrukturen hast. Inkonsistenz ist vorprogrammiert.

Was du mit `zip` fuer eindimensionale Listen gefunden hast, laesst sich auch auf mehr dimensionale Anwenden, zumindest in deinem Fall.

1. Zippe die beiden Strukturen
2. Sortiere (mit passendem `key` natuerlich)
3. Trenne die beiden Listen wieder.

Am besten waere aber, du laesst 1 & 3 weg und baust eine geeignete Datenstruktur auf.

Und wenn du schon PEP8 erwaehnst: Eine Sprache (neben Python) reicht, nimm `table` statt `tabelle`.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Das riecht mir schon mal nach einem Design-Fehler, wenn du 2 so stark verwandte Daten in getrennten Datenstrukturen hast. Inkonsistenz ist vorprogrammiert.
Nein, das passt schon :lol: Diese zweite Liste war ursprünglich in der ersten intergriert, dies wurde aber zurecht hier kritisiert (http://www.python-forum.de/viewtopic.php?f=1&t=31847)
Diese zweite Liste ist nur temporär und beinhaltet auch andere Werte, statt 1,2,3,4 - lediglich 'True' und 'False' und dient als Index für ausgewählte Datensätze. '1,2,3,4' wählte ich hier um verständlich zu machen was ich umsetzen will.

Dieses gemeinsame Sortieren mittels zip fand ich bereits, kann das aber nicht so umsetzen dass eine beliebige Spalte sortiert wird, der genaue Syntax wie ich das in diese lambda Zeile einbauen kann .... :cry:
Und wenn du schon PEP8 erwaehnst: Eine Sprache (neben Python) reicht, nimm `table` statt `tabelle`.
Konsequenterweise dann aber auch 'COLUMN' statt 'SPALTE' oder eben 'column' statt 'spalte'....was die eigentliche Frage zu PEP 8 war ;)
BlackJack

@tom1968: Die Kritik war, dass dieses Flag Teil der Nutzdaten war und nicht das es Teil eines Datensatzobjektes wäre. Eine separate Liste halte ich sogar für schlechter als wenn man es als Spalte macht, die da eigentlich nichts verloren hätte. Noch besser wäre es IMHO die Markierungen ganz sein zu lassen.

Edit: Wenn etwas ein Argument ist, dann ist es offensichtlich keine Konstante, also `column`/`spalte`.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

@tom1968: Die Kritik war, dass dieses Flag Teil der Nutzdaten war und nicht das es Teil eines Datensatzobjektes wäre. Eine separate Liste halte ich sogar für schlechter als wenn man es als Spalte macht, die da eigentlich nichts verloren hätte. Noch besser wäre es IMHO die Markierungen ganz sein zu lassen.
Ich fand die Kritik (so wie ich sie verstanden habe) deshalb für gerechtfertigt weil dieses Klassenksontrukt nur mit Tabellen funktioniert die diese Flag Spalte haben und weil diese Flag Spalte nicht zu den Nutzerdaten gehört.
Was haltet ihr, als Alternativ zu einer 'separate Liste' -Lösung davon:
In der __init__ der Klasse wird der Tabelle temporär diese Flag-Spalte angefügt- wenn die Datensätze gespeichert werden sollen wird diese Spalte zuvor wieder abgeschnitten ??
Damit entfiele das parallele Sortieren und die Methoden der Klasse würden dennoch mit JEDER 2-dimensionalen Tabelle funktionieren.

Unabhängig davon hier nochmal mein erfolgloser Versuch:

Code: Alles auswählen

# Funktioniert, sortiert nach der gewünschten Spalte, aber nur einer Liste
self.tabelle = deepcopy(sorted(self.tabelle, key=lambda x: x[SPALTE]))

# Funktioniert so wie gewollt, sortiert aber nur nach der 1. Spalte der 'tab_list'
list1, list2 = zip(*sorted(zip(tab_list, flag_list)))

# Mein Versuch beides zu kombinieren ergibt: >>> SyntaxError: invalid syntax (<pyshell#12>, line 1)
list1, list2 = zip(*sorted(zip((tab_list, key=lambda x:x[2]), flag_list)))
Edit: Wenn etwas ein Argument ist, dann ist es offensichtlich keine Konstante, also `column`/`spalte`.
Danke, das ist nachvollziehbar.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Laden und Speichern von Daten hat erstmal nichts mit irgendeiner internen Datenstruktur zu tun, weshalb man auch keine Spalten hinzufügen oder abschneiden müßte, sondern man muß nur sagen, die interne Daten haben weitere Flags, die nur zu Berechnungen benötigt werden.

Aber um Dir sagen zu können, wie das Flag-Problem am elegantesten zu lösen ist, müßten wir erst einmal wissen, wozu Du es überhaupt brauchst.

PS: zip hat kein Key-Argument, Tupel erst recht nicht.

Code: Alles auswählen

list1, list2 = zip(*sorted(zip(tab_list, flag_list), key=lambda x:x[0][SPALTE])))
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

@Sirius3

Code: Alles auswählen

list1, list2 = zip(*sorted(zip(tab_list, flag_list), key=lambda x:x[0][SPALTE])))
...ergibt bei mir wieder einen 'SyntaxError: invalid syntax (<pyshell#17>, line 1)'

Zu Deiner Frage:
Diese Flag- oder Index Spalte oder Liste wird benötigt damit Datensäte zur Ausgabe selektiert werden können.
Beispiel: Bücherliste; Selektiert werden sollen alle Sachbücher, die erhalten nun den Index True, alles andere False.
Bei der Ausgabe werden alle Datensätze mit Index False ignoriert.

Wenn man nun eine separate Liste verwendet und die Datensätze bzw. deren Reihenfolge nach dem Selektieren der Sachbücher ändert, z.B. nach Autor oder Erscheinungsjahr sortiert - dann passt ohne parallele Sortierung der Index Liste diese nicht mehr zu den Datensätzen.

Meine Lösung bisher ist folgende: In meiner Testtabelle/Liste habe ich eine Spalte mit einer fortlaufenden Nummer, die benötige ich eigentlich nur um die Datensätze immer wieder in die Original-Eingabe-Reihenfolge sortieren zu können - was durchaus Sinn machen kann, hier aber erstmal nur zu Testzwecken dient. Diese laufende Nummer ist sozusagen ein eindeutiger Schlüssel der Datensätze.
Als externe Flag Liste nutze ich nun ein Dictionary {laufende Nummer : True/False}.
Funktionieren tut das wunderbar (weil ich da nichts parallel mitsortieren muss), hat aber eben den Nachteil dass es nur solange funktioniert solange eine Tabelle/Liste eine Spalte mit fortlaufender Nummer hat.
BlackJack

@tom1968: Das hatten wir im Grunde ja alles schon mal. Statt Datensätze zu markieren würde man einfach eine Liste mit den ausgewählten Datensätzen erstellen. Beziehungsweise ein komplettes Tabellenobjekt mit den ausgewählten Datensätzen erstellen, damit man wieder die gleichen Operationen zur Verfügung hat.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Ja stimmt, der Grund warum ich es so machen will ist dass es dadruch sehr einfach ist, Datensätze zu verändern, Bemerkungen ergänzen etc.
Der Inhalt wird einfach in entry Feldern ausgegeben, springt man zum nächsten Datensatz werden alle Entry Felder ausgelesen und in dem (aktuellen) Datensatz in der Tabelle überschrieben.
Arbeite ich nur mit einer sortierten Kopie der Tabelle muss ich die geänderten Datensätze der Originaltabelle zuweisen und brauche dafür wieder eine Key Spalte.
Vorteile mit einer Kopie zu arbeiten sehe ich, abgesehen vom Verzicht auf einer Flag-Spalte oder Liste nicht.
Bin da aber für Argumente offen ;)
BlackJack

@tom1968: Du müsstest nur die Datensätze selbst verändern, dann ist es völlig egal aus welchem Tabellenobjekt die kommen. Das geht bei Dir im Moment nicht weil Du soweit ich das in Erinnerung habe immer Kopien von den Datensätzen erstellst. Aus etwas was ich nicht machen würde.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Ja, stimmt, ich hole mir eine Kopie des aktuellen Datensatzes, der wird dann auf die Entrys aufgeteilt.
Anschließend, sobald man zum nächsten Datensatz springt, wird per tabelle.set_new_content_in_row(Datensatz aus den Entrys)
wieder in die vollständige Originalliste eingelesen - siehe Code:

Code: Alles auswählen

"""
Datensatz 1
Datensatz 2
Datensatz 3
Datensatz 4 <<<aktuell am Bildschirm ausgegebener Datensatz
Datensatz 5
Datensatz 6
"""

# '>' Button wird gedrückt

def naechsteZeile(self):
    zeile = self.zspeichern()     # holt die Inhalte der Entrys (von Datensatz4) und speichert sie in der Liste 'zeile'
    tabelle.set_new_content_in_row(zeile)   #die Liste 'zeile' wird in die Originaltabelle (bei Datensatz4) eingelesen
    self.ausgabe(tabelle.get_next_row())    #der nächste Datensatz (Datensatz5 sofern dessen Index True ist) wird geholt und
                                                          # per 'self.ausgabe' auf die Entrys verteilt
Vielleicht, um die Verwirrung nicht ins unendliche zu treiben :lol: , das Ganze Konstrukt besteht aus zwei Klassen, das Hauptprogramm besteht aus einer GUI Klasse (deutsche Methodennamen), dieses Hauptprogramm importiert die Tabellenklasse (engliche Methodennamen).
Jegliches direkte Ansprechen der Tabelle erfolgt ausschließlich in der Tabellenklasse mittels der dortigen Methoden.
Das Hauptprogramm lädt die Tabelle lediglich beim Programmstart und übergibt diese Liste als Argument beim ertmaligen Aufruf der Tabellenklasse (Instanzbildung)


Dennoch, nur damit ich dich (BlackJack) richtig verstehe: Wenn ich Datensätze selektiere, und eine Kopie der Originalliste/Tabelle - die dann nur die selektierten Datensätze enthält für die Ausgabe verwende - habe ich doch hinterher das gleiche Problem, nämlich eine Kopie zu haben die wieder mit der vollständigen Originalliste abgeglichen werden muss. Richtig so ?
Wenn ja....halte ich mein Konzept für einfacher
BlackJack

@tom1968: Die Tabelle mit den selektierten Datensätzen enthält ja die *selben* Datensätze wie die komplette Tabelle. Jeder Datensatz existiert also nur einmal als Objekt und wenn man das Objekt verändert, ändern man *den* Datensatz für das jeweilige Datum.
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

puhh :shock: .... ok das ist dann ja ein ganz anderer Ansatz, jeder Datensatz ist ein Objekt und nicht nur die Tabelle als Ganzes.

Ehrlich gesagt traue ich mir die Umsetzung, momentan noch nicht zu, vor allem wirft das ja alles bisher daran gemachte komplett über den Haufen (ok, zweiteres ist ein schlechtes Argument :lol: ).
Ich muss das erst mal im Kleinen ausprobieren, werde aber zuvor das "Projekt", so wie angedacht, erstmal zu Ende programmieren.
Trotzdem Danke fürs Mitdenken oder "Reindenken" !!
BlackJack

@tom1968: So anders ist der Ansatz doch gar nicht. Du müsstest halt nur aufhören immer Kopien von den einzelnen Objekten anzufertigen, so das jeder Datensatz durch genau eine Liste repräsentiert wird. Objekt ist in Python übrigens *jeder* Wert, die Begriffe kann man synonym verwenden. Alles was man an einen Namen binden kann, ist in Python ein Objekt und damit auch ein Wert. Inklusive Module, Funktionen, Klassen und Methoden.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nein, das veraendert nichts. Die Listen, die deine Tabellenzeilen repraesentieren sind auch Objekte. Wenn du jetzt eine Tabelle aus der alten aber nicht mit allen Zeilen erstellst, bleiben die Zeilenobjekte (d.h. die Listen) immernoch dieselben, d.h. veraenderst du eine Zeile in der alten Tabelle, gibt es die Aenderung auch in der neuen.

Das setzt natuerlich voraus, dass die neue Tabelle eine flache Kopie der alten ist.

Code: Alles auswählen

In [1]: table =  [range(3), range(3, 5), range(5, 8)]

In [2]: table
Out[2]: [[0, 1, 2], [3, 4], [5, 6, 7]]

In [3]: new_table = [row for i, row in enumerate(table) if i % 2]

In [4]: new_table
Out[4]: [[3, 4]]

In [5]: new_table[0][1] = 23

In [6]: new_table
Out[6]: [[3, 23]]

In [7]: table
Out[7]: [[0, 1, 2], [3, 23], [5, 6, 7]]
tom1968
User
Beiträge: 38
Registriert: Sonntag 30. Juni 2013, 09:54

Ja, bei Tageslicht besehen scheint eine Umstellung gar nicht so gravierend zu sein....

Thomas
Antworten