Hausnummern und Straßen trennen

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
kruphi
User
Beiträge: 21
Registriert: Donnerstag 25. Juli 2013, 14:57

Hallo,

ich bin neu in dieser Sprache, ich hoffe ihr könnt mir da evtl. helfen.
Ich schreibe gerade ein Programm das mir XML Daten für ein anderes Programm aufbereitet.
Mein Problem, die Strassen stehen in dem XML in einem Knoten.
"Hauptstraße 80"
"Am Schlosswall 13"
" vor dem Brunnen 9b"

Ich müsste die Hausnummern von Straßennamen trennen. Hier im Forum habe ich einige Ansätze mit split gefunden.
Leider ohne großen Erfolg.

Als Erklärung. So sieht meine Lösung vereinfacht aus.




Code:

Code: Alles auswählen

for i in all:
        a=all[i].strasse.encode("utf-8")
        for i in list(a):
            try:
                if isinstance( int(i), int ):
                print i,

Vielen Dank schon mal für eure Hilfe!
Zuletzt geändert von cofi am Donnerstag 25. Juli 2013, 16:10, insgesamt 2-mal geändert.
Grund: In [python] Tags gesetzt
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Willkommen im Forum und so wie es aussieht auch zu Python!

Zu deiner Loesung .. die ist einfach kaputt.
Zuerst solltest du deine XML-Dateien mit einem XML-Parser oeffnen, die sollten dir schon _dekodierte_ Strings zurueckgeben. Wenn du etwas _kodierst_, kommt eine Bytesequenz heraus. Sowas willst du ausserhalb von Input und Output nicht haben.

Das `list(a)` ist ueberfluessig, du erstellst hier eine Liste von Bytes aus einer Bytesequenz .. zum iterieren macht das keinen Unterschied.

`isinstance(int(i), int)` ist immer wahr ... oder wirft einen `ValueError` weil `i` keine Zahl ist oder als (Dezimal-)Zahl gelesen werden kann, insofern kannst du auch direkt `int(i)` nehmen.

Hier ein simpler Ansatz, von dem ich allerdings nicht weiss ob er korrekt ist - dafuer habe ich zu wenig wissen ueber Hausnummern:

Code: Alles auswählen

In [1]: streets = ["Hauptstraße 80",
   ...:            "Am Schlosswall 13",
   ...:            " vor dem Brunnen 9b"]

In [2]: 

In [2]: for street in streets:
   ...:         street, number = street.rsplit(' ', 1)
   ...:         print street
   ...:         print number
   ...:     
Hauptstraße
80
Am Schlosswall
13
 vor dem Brunnen
9b
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Und hier noch ein paar Hinweise zu Straßennamen und Hausnummern.
Das Leben ist wie ein Tennisball.
kruphi
User
Beiträge: 21
Registriert: Donnerstag 25. Juli 2013, 14:57

Erstmal riesen Dank für deine Antwort. Das hat mir auf jeden Fall schon einmal einen guten Tip gegeben.
Habe gerade einmal schnell versucht dies umzusetzen, leider nur mit einem Teil erfolg.

Die Daten habe ich schon aus der XML in ein dictionary geprasst.
Jetzt will ich nur noch Hausnummern von Strassen trennen. Dazu habe ich mir( an dieser Stelle noch einmal ein großes Danke schön) teile von deinem Code geklaut :lol:.

Code: Alles auswählen

def getHausNr(a): 
    street, number = a.rsplit(' ', 1)
    return number
jetzt will ich in meinem Hauptprogramm diese Funktion aufrufen:

Code: Alles auswählen

for i in all:
    getHausNr(all[i].strasse)
da bekomme ich folgenden Fehler:
ValueError: need more than 1 value to unpack
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nun, deine Daten sehen wohl anders aus als du denkst:

Code: Alles auswählen

In [1]: street, number = 'Strasse1'.rsplit(' ', 1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-0806250e223f> in <module>()
----> 1 street, number = 'Strasse1'.rsplit(' ', 1)

ValueError: need more than 1 value to unpack
Und gib in Zukunft bitte den _ganzen_ Traceback an, statt nur der Fehlermeldung.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

EyDu hat geschrieben:Und hier noch ein paar Hinweise zu Straßennamen und Hausnummern.
Wobei es in dem von dir verlinkten Thread ja hauptsächlich darum geht, schlecht formatierte Adresseingaben wieder gerade zu bügeln. Sowas ist schon fast unlösbar, wenn man wirklich jeden erdenklichen Fall korrekt behandeln möchte.

Aber hier ist es ja so, dass die Daten offenbar sauber eingegeben wurden. Wenn also garantiert ist, dass nach dem am weitesten rechts stehenden Leerraum auf jeden Fall die Hausnummer kommt und dass ein ggf folgender Buchstabe direkt an der Hausnummer hängt, dann ist `.rsplit(' ', 1)` IMHO schon sehr robust. Fies wird es natürlich, wenn bei der Hausnummer noch sowas wie "Zimmer 3" oder so steht.

Jedenfalls ist es bei den vorgenannten einfachen Fällen von Hausnummer-Angaben doch an sich ziemlich egal, wie der zugehörige Straßenname aussieht. Man splittet halt einfach am letzten Leerzeichen und ist glücklich. Oder wolltest du auf etwas anderes hinaus?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@kruphi: Versuch mal folgendes, damit man in der Schleife besser sieht, bei welchem String es kracht:

Code: Alles auswählen

def getHausNr(a):
    try:
        street, number = a.rsplit(' ', 1)
    except ValueError:
        print 'Fehler bei:', a
        raise
    return number
BlackJack

@snafu: 'Strasse 105' kann ein gültiger *Strassenname* sein. Ohne Hausnummer.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@kruphi: Du solltest noch an Deinen Variablennamen arbeiten. »all« ist eine builtin Funktion und sollte nicht als Variablenname verwendet werden. »i« läßt auf eine Zahl schließen und nicht auf einen Dictionary-Key.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

BlackJack hat geschrieben:@snafu: 'Strasse 105' kann ein gültiger *Strassenname* sein. Ohne Hausnummer.
Wie bereits geschrieben: Es kommt darauf an, welche Garantien für die Datenlage gegeben werden. Was soll man denn aus solchen Einwänden schließen? Dass eine automatisierte Auswertung gar nicht erst begonnen werden soll, weil einer von 10.000 Fällen durch's Raster fallen könnte...? :o

Es gibt halt Fälle - wie den von dir gezeigten -, die man ganz einfach nicht (erfolgreich) automatisiert behandeln kann. Da muss man dann ggf die Ergebnisse noch durch eine Adresssuche jagen und die fehlerhaften Daten per Hand korrigieren. Das einzige, was dies zeigt, ist dass man sich in solchen Fällen nicht bloß auf die Ergebnisse eines eher naiven Ansatzes verlassen kann. Trotzdem spart es ja schon jede Menge Arbeit, zunächst so vorzugehen.
lunar

@snafu Vielleicht eher, dass man den Nutzer die Adresse direkt richtig eingeben lässt, oder besser gar nicht erst nach Straße und Hausnummer trennt, wenn es nicht zwingend notwendig ist. Je nach Einzugsbereich der Anwendung gerät man sonst nämlich schnell in Teufels Küche, denn es gibt auch Adresse ohne numerische Hausnummer, oder gar ganz ohne Hausnummer.
kruphi
User
Beiträge: 21
Registriert: Donnerstag 25. Juli 2013, 14:57

Vielen Dank an dieser Stelle für eure Tips/Lösungen!
Diese haben mir sehr geholfen und ich konnte das Problem lösen.

Um die Lösung für andere evtl. zu vervollständigen beschreibe ich das gerade ein schnell hier.

Mit den Lösungsvorschlag von snafu

Code: Alles auswählen

def getHausNr(a):
    try:
        street, number = a.rsplit(' ', 1)
    except ValueError:
        print 'Fehler bei:', a
        raise
    return number

konnte ich erkennen bei welchem Datensatz der Fehler passierte. Draus ließ sich erkennen, einige der Straßen waren Fehlerhaft eingegeben und erzeugten so den Fehler.
Ich habe (natürlich mit eurer Hilfe) das Problem wie folgt gelöst!

Code: Alles auswählen

def getHausNr(a):    
        try:
              street, number = a.rsplit(' ', 1)
              return number
        except ValueError:
                print 'Fehler bei:', a
                log.fehler("Strassennummer auslesen ", " Kein Strassennummer vorhanden")
                return 0 
Ich wollte mich an dieser Stelle nur noch einmal bedanken!
BlackJack

@kruphi: Strassennummer und Hausnummer sind IMHO nicht synonym. Du solltest Dich da vielleicht auf die Bezeichnung Hausnummer beschränken.

Bei fehlender Hausnummer würde ich eher die leere Zeichenkette statt 0 verwenden, denn sonst würde die Funktion je nach Argument eine Zeichenkette oder eine Zahl zurückgeben.

Code: Alles auswählen

def get_house_number(street):
    _street_name, _, number = street.rpartition(' ')
    if not number:
        LOG.error(
            'Strassennummer auslesen, keine Strassennummer vorhanden: %s',
            street
        )
    return number
`LOG` ist in diesem Fall ein `Logger` aus dem `logging`-Modul. Man muss das Rad ja nicht neu erfinden.
kruphi
User
Beiträge: 21
Registriert: Donnerstag 25. Juli 2013, 14:57

@BlackJack
die log Geschichten habe ich mir damals als Übung selbst zusammen geschrieben.
Als Lernender muss man das Rad evtl. einfach mal neu erfinden um zu entdecken bzw. kennen zu lernen!

Oder sollte ich solche Dinge lieber durch bekannte Module ersetzten? Ich dachte mir, es frisst so ja kein Brot.......also lasse ich es ??
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich würde beim produktiven Einsatz wohl eher einen Integer anstatt einer Zeichenkette zurückliefern. Wenn keine Hausnummer ermittelt werden konnte, stellt dies - je nach Sichtweise - nicht zwangsläufig einen Fehler dar. Deshalb würde meine Variante in dem Fall einfach `None` zurückliefern, denn die Hausnummer 0 könnte es ja theoretisch trotzdem geben. `None` hat außerdem den Vorteil, dass es recht schnell kracht, wenn jemand die Rückgabe nicht überprüft. Meistens bevorzugt man ja eine Exception anstatt den Anwender zur Prüfung des Rückgabewertes zu nötigen, aber ich denke, in diesem Fall ist `None` schon okay.

Unter Verwendung des `logging`-Moduls könnte eine mögliche Herangehensweise dann so aussehen:

Code: Alles auswählen

#!/usr/bin/env python
import logging

def get_house_number(address):
    try:
        street, number = address.strip().rsplit(' ', 1)
        return int(number)
    except ValueError:
        return None

def get_house_numbers(addresses, logger=None):
    result = []
    for address in addresses:
        house_number = get_house_number(address)
        if house_number is None and logger is not None:
            msg = 'Failed to determine house number: {!r}'
            logger.warning(msg.format(address))
        result.append(house_number)
    return result


def main():
    logging_format = '%(levelname)s: %(message)s'
    logging.basicConfig(filename='errors.log', format=logging_format)
    with open('addresses.txt') as addressfile:
        house_numbers = get_house_numbers(addressfile, logging.getLogger())
    print house_numbers


if __name__ == '__main__':
    main()
BlackJack

@kruphi: Zum lernen kann man Räder natürlich neu erfinden, aber ich hatte den Eindruck hier ging es eher darum tatsächlich zu loggen. Und da würde ich was vorhandenes nehmen, wenn es denn schon von der Standardbibliothek angeboten wird.

Logging braucht man ja öfter mal, und dann müsste man das ja entweder jedes mal neu schreiben, oder selber so etwas wie ein `logging`-Modul ausgliedern und pflegen.

@snafu: Wie stellst Du die Hausnummern '42a' oder '23-24' oder '4711 A-E' als ganze Zahlen dar? ;-)
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Stimmt, das mit dem Integer war ein Schnellschuss. Es muss zwangsläufig bei einem String bleiben.

Bei '4711 A-E' würde ich wahrscheinlich einfach sagen: "Pech gehabt" und es zur manuellen Überprüfung geben.

Man läuft beim naiven Splitten natürlich immer Gefahr einige False Positives zu haben. Theoretisch müsste man da wahrscheinlich mit irgendeinem monströsen regulären Ausdruck dran, was dann der Beweis wäre, dass `.rsplit(' ', 1)` im Endeffekt doch keine so gute Idee ist. ^^
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier dann mal ein erster Ansatz mit Regex:

Code: Alles auswählen

import re

def get_house_number(address):
    pattern = re.compile(
        r'(?<=[ ])\d+(([/-]\d+)|[a-z](-[a-z])?)?$', re.IGNORECASE)
    result = pattern.search(address.strip())
    if not result:
        return ''
    return result.group()
Ist gedacht für alle validen (deutschen) Varianten ohne Leerzeichen dazwischen - wobei Dinge wie ½ leider nicht erkennt werden. Zusätze wie "Zimmer 42" funktionieren ebenfalls nicht.

Und hier nochmal in Verbindung mit `.split()`:

Code: Alles auswählen

def get_house_number(address):
    house_number = address.split()[-1]
    pattern = re.compile(r'\d+(([/-]\d+)|[a-z](-[a-z])?)?$', re.IGNORECASE)
    if not pattern.match(house_number):
        return ''
    return house_number
Sieht zwar kompliziert aus, aber gibt zumindest die Garantie, dass alle automatisch gefundenen Nummern, auch "echte" Hausnummern sind. Wie gesagt: Den Rest müsste man manuell überprüfen. Wie man diese aussortiert, wurde ja schon im Thread beschrieben.

Das einzige, was jetzt noch passieren kann, ist dass ein Straßenname mit einer Zahl endet, diese Zahl mit einem Leerzeichen getrennt wurde und auf diese Zahl (bzw diesen Straßennamen) *keine* Hausnummernangabe folgt. Das heißt der Straßenname "Straße 42" ohne Hausnummernangabe würde falsch behandelt werden.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

lunar hat geschrieben:@snafu Vielleicht eher, dass man den Nutzer die Adresse direkt richtig eingeben lässt, oder besser gar nicht erst nach Straße und Hausnummer trennt, wenn es nicht zwingend notwendig ist. Je nach Einzugsbereich der Anwendung gerät man sonst nämlich schnell in Teufels Küche, denn es gibt auch Adresse ohne numerische Hausnummer, oder gar ganz ohne Hausnummer.
Ergänzend dazu der Link Falsehoods programmers believe about addresses, der verdeutlicht dass man von seiner bisherigen Beobachtung vorschnell auf eine allgemeine Regel schließt (induktive Schlussfolgerung). Es gibt auch andere Seiten mit dem Titel "Falsehoods programmers believe about XY", die ich persönlich sehr interessant und aufschlussreich finde.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

derdon hat geschrieben:Ergänzend dazu der Link Falsehoods programmers believe about addresses, der verdeutlicht dass man von seiner bisherigen Beobachtung vorschnell auf eine allgemeine Regel schließt (induktive Schlussfolgerung).
Falsehoods programmers believe about addresses hat geschrieben:OK, but at the very least you wouldn’t name a town Street
Ich glaube da drängt sich das entsprechende Meme schon direkt auf :D

Aber danke für den Link, finde ich tatsächlich ziemlich interessant.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten