Sortierung mit csv

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
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

Hallo,
ich bin noch ein relativer Python-Neuling...

Ich schreibe momentan an meinem kleinen ersten Programm. In diesem möchte ich eine Art Adressbuch realisieren.
Ich gebe also über tkinter die Felder Vorname, Nachname, Geburtsdatum, Telefonnummer, Anschrift ein und speichere in einer csv-Datei.

Die erste Zeile besteht aus folgendem

Code: Alles auswählen

Vorname,Nachname,Geburtsdatum,Telefonnummer,Anschrift
Gerne möchte ich diese dann sortieren können, z.B. nach Geburtsdatum(das ist das eigentliche Ziel!!!) oder Nachname.

Ich nutze dazu diesen Code:

Code: Alles auswählen

def sortieren():
    import csv     # imports the csv module

    f = open("personalien.csv") # opens the csv file
    reader = csv.DictReader(f)  # creates the reader object
    liste = list(reader)
    print("unsortierte Liste: \n" ,liste)
    liste.sort(key=lambda x: x['Nachname'])
    print("sortierte Liste: \n", liste)

Button(root, text ="CSV-READER", command=sortieren).grid(row=99, column=1, sticky=W, pady=4)

Wenn ich diesen Button drücke, dann werden die ZEilen aus der csv-Datei sogar richtig sortiert. Leider jedoch nicht mehr in der Reihenfolge (Vorname, Nachname, Geburtsdatum, Telefonnummer, Anschrift) sondern es fängt bei Geburtsdatum an. Vorname und Nachname wird hinten angeschlossen. Wenn ich dann nur "Geb" schreibe, dann wird nach der Telefonnummer sortiert.
Warum ist das so??

Ich möchte natürlich gerne weiterhin mit dem Vornamen beginnen und nach Nachname sortieren....

orientiert habe ich mich hier dran
http://wiki.python-forum.de/Sortierungs-Tutorium

Vielen Dank.

MFG
BlackJack

@cheffe: Wenn Du nur 'Geb' schreibst dann wird sicher nicht nach Telefonnummer sortiert sondern es gibt einen `KeyError` weil es diesen Schlüssel in den Elementen der Liste nicht gibt.

Nur so geraten: Du weisst das die Reihenfolge von Schlüssel/Wert-Paaren in Wörterbüchern (`dict`) ”zufällig” ist‽ Wenn Du die Reihenfolge der Schlüssel in der Datei wissen möchtest, dann gibt es das `fieldnames`-Attribut auf dem `DictReader`-Objekt.
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

ich meinte, wenn ich in der csv-datei das geburtsdatum in der ersten Zeile nur "geb" nenne...
dann lief alles auch weiter, aber mit einer etwas anderen reihenfolge... aber ich glaube, dass ist hier zweitrangig...

die zweite info scheint wohl die lösung zu sein. und "nein", ich wusste das nicht... leider ist mir mit deiner info noch nicht wirklich geholfen. würdest du mir das vielleicht einmal passend einbauen?

mfg ;)
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@cheffe: ein Wörterbuch hat keine Reihenfolge. Wo willst Du was passend eingebaut haben? Auf Wörterbücher greift man über einen Schlüssel zu, so wie Du es ja auch bei Deinem sort gemacht hast.
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

meine csv datei sieht ungefähr wie folgt aus:

Code: Alles auswählen

Vorname,Nachmame,Geburtsdatum,Telefonnummer,Adresse

Max,Mustermann,01.01.1999,01234-123456,Musterstadt
Andrea,Anders,02.02.1987,02345-123523,Berlin
davon gibt es natürlich auch noch beliebig viele andere Zeilen...

ich möchte gerne, dass mir die print-ausgabe auch in der Reihenfolge Vorname>Nachname>Geburtsdatum>Telefonnummer>Anschrift ausgegeben wird, nur halt sortiert nach Nachnamen (oder Geburtsdatum)
erstmal reicht es, wenn die Einträge des dict in der festen Reihenfolge ausgegeben wird

später soll das ganze dann mittels tkinter noch in ein LAbel ausgegeben, aber das traue ich mir dann selber zu...
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

um die Adressen ausgeben zu können, mußt Du die Datensätze ja erst in einen String umwandeln. Das macht man mit der .format-Methode, bei der man ja die Reihenfolge angeben muß:

Code: Alles auswählen

print("sortierte Liste:")
for address in addresses:
    print("{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Telefonnummer]}".format(address))
BlackJack

@cheffe: Wie gesagt: Die Schlüssel gibt es als Sequenz in der richtigen Reihenfolge als Attribut auf dem `DictReader`-Objekt. Das kannst benutzen um bei der Ausgabe auf der Konsole oder in einer GUI die Daten in der gewünschten Reihenfolge anzuzeigen.
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

Sirius3 hat geschrieben:um die Adressen ausgeben zu können, mußt Du die Datensätze ja erst in einen String umwandeln. Das macht man mit der .format-Methode, bei der man ja die Reihenfolge angeben muß:

Code: Alles auswählen

print("sortierte Liste:")
for address in addresses:
    print("{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Telefonnummer]}".format(address))
meine Lösung sieht nun erstmal wie folgt aus:

Code: Alles auswählen

def sortieren():
    fenster_sortierteliste = Tk()
    fenster_sortierteliste.title("sortierte Liste")
    import csv     # imports the csv module

    csvdatei = open("personalien.csv") # opens the csv file
    reader = csv.DictReader(csvdatei, fieldnames = ( "Vorname","Nachname","Geburtsdatum","Handynr","Festnetznr","Adresse"))  # creates the reader object
    liste = list(reader)
    liste.sort(key=lambda x: x['Vorname'])
    for datensatz in liste:
        Label(fenster_sortierteliste, text=("{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))).grid()
damit funktioniert es zumindest solange ich nach dem nachnamen sortieren will

@ BlackJack: aus deiner Antwort werde ich leider noch immer nicht schlau. da muss ich wohl noch etwas in den python tutorials nachlesen :(


könnt ihr mir vielleicht auch gerade nen tipp geben, wie ich nach nem Datum sortieren kann?
ich möchte zunächst erstmal nur, dass die Person mit dem 01.01 als Geburtstag ganz oben steht, und der Geburtstag am Ende des Jahres dann unten ;)


((Zusatz: und falls jemand die Muße hat: könnte mir jemand erklären warum das hier so funktioniert?

Code: Alles auswählen

 text=("{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))
ich lese schon im internet nach ;) ))
BlackJack

@cheffe: Warum dann eigentlich einen `DictReader`? Wenn Du die Spaltenköpfe im Quelltext vorgibst und gar nicht wirklich den Zugriff über Schlüssel benötigst und alle Spalten in der Reihenfolge aus der Datei anzeigen willst, kannst Du auch einfach `csv.reader()` verwenden.

Was an meiner Antwort ist denn unklar? Du weisst welches Objekt in Deinem Code vom Typ `DictReader` ist? Du weisst was ein Attribut ist? Du kannst Dir das dann vielleicht einfach mal *ausgeben* lassen um zu sehen welchen Wert das hat‽

Um nach Datum zu sortieren würde ich die Datumsangaben erst einmal alle in den passenden Typ umwandeln. Den gibt's in der Python-Standardbibliothek im `datetime`-Modul. Um eine Zeichenkette zu parsen muss man dort von `datetime` die `strptime()`-Methode verwenden, auch wenn man nur das Datum braucht. Der Zeitanteil ist dann einfach Null für alle Teile der Zeit (Stunde, Minute, …). Sortieren willst Du ja nur nach Monat und Tag, also musst Du eine entsprechende `key`-Funktion schreiben die aus dem jeweiligen Datumsobjekt einen Sortierschlüssel erstellt. Also Beispielsweise ein Tupel aus Monat und Tag. In *der* Reihenfolge, weil der Monat ein höheres Gewicht beim sortieren erhalten muss.
Ene Uran
User
Beiträge: 125
Registriert: Sonntag 17. September 2006, 20:14
Wohnort: Hollywood

Vielleicht so was ...

Code: Alles auswählen

import pprint

def sortier_helfer(item):
    # Geburtsdatum ist item[2] in der Liste
    t, m, y = item[2].split('.')
    return (m, t, y)

# csv type file
# Geburtsdatum Format  Tag.Monat.Jahr
# Vorname,Nachmame,Geburtsdatum,Telefonnummer,Adresse
data = '''Max,Mustermann,30.11.1999,01234-123456,Musterstadt
Andrea,Anders,27.01.1987,02345-123523,Berlin
Sven,Siegel,15.06.1970,03456-654321,Darmstadt'''

fname = "freunde.csv"
with open(fname, "w") as fout:
    fout.write(data)

with open(fname) as fin:
    mylist = [[item for item in line.strip().split(',')] for line in fin]

pprint.pprint(mylist)

'''
[['Max', 'Mustermann', '30.11.1999', '01234-123456', 'Musterstadt'],
 ['Andrea', 'Anders', '27.01.1987', '02345-123523', 'Berlin'],
 ['Sven', 'Siegel', '15.06.1970', '03456-654321', 'Darmstadt']]
'''

print('-'*60)

mylist.sort(key=sortier_helfer)

pprint.pprint(mylist)

'''
[['Andrea', 'Anders', '27.01.1987', '02345-123523', 'Berlin'],
 ['Sven', 'Siegel', '15.06.1970', '03456-654321', 'Darmstadt'],
 ['Max', 'Mustermann', '30.11.1999', '01234-123456', 'Musterstadt']]
'''
Atomkraftwerkaktienbesitzer
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

mein code dafür sieht momentan so aus:

Code: Alles auswählen

    elif sortierauswahl.get() == "Geburtsdatum":

        def sortier_helfer(item):
            # Geburtsdatum ist item[2] in der Liste
            t, m, y = item[2].split('.')
            return (m, t, y)

        fname = "personalien.csv"
        with open(fname) as fin:
            mylist = [[item for item in line.strip().split(',')] for line in fin]

        pprint.pprint(mylist)
        print('-'*60)
        mylist.sort(key=sortier_helfer)
        pprint.pprint(mylist)
damit bekomme ich es jetzt im konsolen-fenster auch gut sortiert...
aber wie bekomme ich das ganze jetzt umgewandelt um es im tkinker fenster anzuzeigen??

sortiert nach Vor-/Nachname sieht es bei mir so aus:

Code: Alles auswählen

    if sortierauswahl.get() != "Geburtsdatum":
        ## wenn nach vorname oder Nachname sortiert wird
        reader = csv.DictReader(csvdatei, fieldnames = ( "Vorname","Nachname","Geburtsdatum","Handynr","Festnetznr","Adresse"))  # creates the reader object
        liste = list(reader)
        liste.sort(key=lambda x: x[sortierauswahl.get()])
        for datensatz in liste:
            Zeile = Text(root, height=1, width=100,)
            Zeile.grid(row=zeilen_counter, column=1, columnspan=10)
            Zeile.insert(END, "{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))
            Zeile.configure(state="disabled")
            zeilen_counter += 1

wenn ich das versuche zu verbinden, dann sieht das bei mir so aus:

Code: Alles auswählen

    ## wenn nach Geburtsdatum sortiert wird
    elif sortierauswahl.get() == "Geburtsdatum":

        def sortier_helfer(item):
            # Geburtsdatum ist item[2] in der Liste
            t, m, y = item[2].split('.')
            return (m, t, y)

        reader = csv.DictReader(csvdatei, fieldnames = ( "Vorname","Nachname","Geburtsdatum","Handynr","Festnetznr","Adresse"))  # creates the reader object
        liste = list(reader)
        for datensatz in liste:
            Zeile = Text(root, height=1, width=100,)
            Zeile.grid(row=zeilen_counter, column=1, columnspan=10)
            Zeile.insert(END, "{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))
            Zeile.configure(state="disabled")
            zeilen_counter += 1
da entsteht auch erstmal kein fehler (mehr). es wird auch sortiert, aber ich weiß nicht wonach jetzt sortiert wird... nach geburtsdatum funktioniert es nicht richtig ;)
Zuletzt geändert von cheffe am Samstag 8. August 2015, 17:53, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@cheffe: Du hast eine Zeile, die weniger als 3 Elemente enthält, also z.B. eine Leerzeile am Ende. Warum benutzt Du nicht mehr das csv-Modul? Hast Du für jede Möglichkeit der sortierauswahl quasi den selben Code stehen?
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

ich glaube du hast nicht mehr aktualisiert was ich zuletzt editiert habe, oder?? sry, mein verschulden ;)
also ich benutze CSV noch ...

soll ich mal hier irgendwo die py-dateien hochladen? (wo geht sowas?)



und ich hab jetzt auch erkannt, wonach beim geburtsdatum jetzt "sortiert" wird. es wird nämlich gar nicht mehr sortiert, sondern die reihenfolge im CSV-File genommen...
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@cheffe: das ist das Problem, wenn man nachträglich seine Posts radikal ändert, dann machen die Antworten plötzlich keinen Sinn mehr. Zum neuen Problem: wenn Du nicht sortierst, wird auch nicht sortiert. Die beiden Fälle der sortierauswahl unterscheiden sich nur in der Sortierfunktion, es ist also unsinnig die restlichen 90% der Zeilen doppelt zu schreiben. Wenn Du einen zeilen_counter brauchst, nimm enumerate.
cheffe
User
Beiträge: 12
Registriert: Donnerstag 21. Mai 2015, 22:25

ach mist... hab ich gar nicht gemerkt, dass ich das gelöscht habe... :(

Code: Alles auswählen

    ## wenn nach Geburtsdatum sortiert wird
    elif sortierauswahl.get() == "Geburtsdatum":

        def sortier_helfer(item):
            # Geburtsdatum ist item[2] in der Liste
            t, m, y = item[2].split('.')
            return (m, t, y)

        reader = csv.DictReader(csvdatei, fieldnames = ( "Vorname","Nachname","Geburtsdatum","Handynr","Festnetznr","Adresse"))  # creates the reader object
        liste = list(reader)
        liste.sort(key=sortier_helfer)
        for datensatz in liste:
            Zeile = Text(root, height=1, width=100,)
            Zeile.grid(row=zeilen_counter, column=1, columnspan=10)
            Zeile.insert(END, "{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))
            Zeile.configure(state="disabled")
            zeilen_counter += 1
führt zu
Traceback (most recent call last):
File "D:\Programmierung\PythonPortable\Portable Python 3.2.5.1\App\lib\tkinter\__init__.py", line 1456, in __call__
return self.func(*args)
File "D:\Programmierung\PythonPortable\Portable Python 3.2.5.1\Freundesliste\main.py", line 63, in personenliste_erzeugen
liste.sort(key=sortier_helfer)
File "D:\Programmierung\PythonPortable\Portable Python 3.2.5.1\Freundesliste\main.py", line 58, in sortier_helfer
t, m, y = item[2].split('.')
KeyError: 2

EDIT: woooohoooo... ich habs :D und es war wieder mal so einfach... hab einfach die [2] durch [sortierauswahl.get()] ersetzt :D :D :D :D :D

und das alles zusammengefügt sieht es dann so aus.

Code: Alles auswählen

## hier wird das  Uebersichtsfenster generiert/geaendert
def personenliste_erzeugen():
    import csv     # imports the csv module
    zeilen_counter = 3
    csvdatei = open("personalien.csv") # opens the csv file

## SORTIERUNG der csv File Daten
    reader = csv.DictReader(csvdatei, fieldnames = ( "Vorname","Nachname","Geburtsdatum","Handynr","Festnetznr","Adresse"))  # creates the reader object
    liste = list(reader)
    if sortierauswahl.get() != "Geburtsdatum": ## wenn nach vorname oder Nachname sortiert wird
        liste.sort(key=lambda x: x[sortierauswahl.get()])
    elif sortierauswahl.get() == "Geburtsdatum": ## wenn nach Geburtsdatum sortiert wird
        def sortier_helfer(item):
            # Geburtsdatum ist item[2] in der Liste
            t, m, y = item[sortierauswahl.get()].split('.')
            return (m, t, y)
        liste.sort(key=sortier_helfer)

    for datensatz in liste:
        Zeile = Text(root, height=1, width=100,)
        Zeile.grid(row=zeilen_counter, column=1, columnspan=10)
        Zeile.insert(END, "{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))
        Zeile.configure(state="disabled")
        zeilen_counter += 1

    csvdatei.close()
was gäbe es jetzt mal daran zu verbessern?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@cheffe: da gibt es noch einiges:
- wie schon geschrieben, enumerate benutzen
- import an den Anfang der Datei schreiben
- with benutzen
- else benutzen
- gemeinsamen Code hinter den if-else-Block schreiben
- keine globalen Variablen
- keine unnötigen Kommentare, die nichts erklären sondern nur beschreiben, was sowieso schon dasteht, oder schon veraltet sind

Code: Alles auswählen

import Tkinter as tk
import csv

PERSONALIEN_FILENAME = "personalien.csv"

def personenliste_erzeugen(root, sortierauswahl):
    auswahl = sortierauswahl.get()
    if  auswahl != "Geburtsdatum":
        sortier_helfer = lambda item: item[auswahl]
    else:
        def sortier_helfer(item):
            t, m, y = item[auswahl].split('.')
            return (m, t, y)
 
    with open(PERSONALIEN_FILENAME) as csvdatei:
        reader = csv.DictReader(csvdatei, fieldnames=( "Vorname","Nachname","Geburtsdatum","Handynr","Festnetznr","Adresse"))
        datasets = sorted(reader, key=sortier_helfer)
 
    for row, datensatz in enumerate(datasets, 3):
        zeile = tk.Text(root, height=1, width=100, state="disabled")
        zeile.grid(row=row, column=1, columnspan=10)
        zeile.insert(END, "{0[Vorname]} {0[Nachname]}, {0[Geburtsdatum]}, {0[Handynr]}, {0[Festnetznr]}, {0[Adresse]}".format(datensatz))
        
BlackJack

@cheffe: Der Sortierhelfer für Geburtsdaten sollte eventuell etwas robuster sein. Mit den gezeigten Daten gibt das kein Problem, aber bei CSV-Dateien muss man damit rechnen das die auch mal von Hand bearbeitet werden, und dass da nicht jeder Benutzer einstellige Zahlen mit einer führenden Null versieht. Und dann gibt es Probleme wenn man diese Datumsbestandteile als Zeichenketten sortiert und nicht als Zahlen. Mal am Beispiel der Monate:

Code: Alles auswählen

In [9]: months
Out[9]: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']

In [10]: sorted(months)
Out[10]: ['1', '10', '11', '12', '2', '3', '4', '5', '6', '7', '8', '9']
Antworten