String separieren

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
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

Hallo.
Die Aufgabe ist es aus:

Code: Alles auswählen

POLYGON = 'POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))'
dies zu zaubern:
P1=(20,12)
P2=(10,11)
P3=(17,18)
P4=(20,20)
P5=(30,19)
P6=(29,18)
P7=(31,15)
P8=(28,10)
P9=(22,8)
P10=(19,9)
Allerdings klappt es nur mit einem "Trick":

Code: Alles auswählen

V1 - gecheatet (weil umgewandelt) und doch noch nicht gut

POLYGON = ((20, 12), (10, 11), (17, 18), (20, 20), (30, 19), (29, 18), (31, 15), (28, 10), (22, 8), (19, 9))
zaehler = 0

for point in POLYGON:
    zaehler += 1
    print(f'P{zaehler}={point}')
    
Ein andere Ansatz ist folgender:

Code: Alles auswählen

# V2

POLYGON = 'POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))'


STARTING_DELIMITER ='('
SEPARATOR = ' '
STARTING_STRING = 'POLYGON '


position_1 = POLYGON.find(STARTING_DELIMITER, len(STARTING_STRING))
print(position_1)

result = POLYGON[position_1:]
print(result)

# result = result.split(STARTING_DELIMITER)
# print(result)

Aber dieser ist definitiv noch nicht ausgereift. Könntet ihr mir einen Hinweis geben?

Mit Dank und vielen Grüßen
Fabian (verzweifelt)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Einen Zähler zählt man nicht von Hand, sondern benutzt enumerate:

Code: Alles auswählen

POLYGON = [(20, 12), (10, 11), (17, 18), (20, 20), (30, 19), (29, 18), (31, 15), (28, 10), (22, 8), (19, 9)]

for index, (x, y) in enumerate(POLYGON, 1):
    print(f'P{index}=({x},{y})')
Laut Aufgabenstellung soll da ja kein Leerzeichen nach dem Komma stehen.

Mit `find` versucht man eigentlich nicht zu arbeiten, weil das Index-Gehampel recht fehleranfällig ist. Hier würde ich im ersten Versuch mit partition arbeiten.

Code: Alles auswählen

prefix, sep, points = POLYGON.partition('(')
if prefix != "POLYGON " or not points.endswith(')'):
    raise ValueError("polygon entspricht nicht den Erwartungen")
Wie genau ist der Inputstring definiert? Wie sieht es mit Leerzeichen oder Kommas aus?
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Feedback1000: `STARTING_DELIMITER` würde ich die Klammern ja nicht nennen, denn ein *Trenner* ist das ja nicht.

Wie immer ist es sinnvoll ein grosses Problem in mehrere kleinere Teilprobleme zu zerlegen, bis die Teilprobleme so klein sind, das man sie mit einer kleinen Funktion leicht lösen kann. Hier kann man das sogar so zerlegen, das ein kleines Teilproblem mehrfach vorkommt: Geklammerte Werte. Denn die einzelnen Paare sind geklammern und alle Paare ebenfalls.

Erste Aufteilungsschritt die Eingabe in eine passende Datenstruktur überführen und die Datenstruktur in die Ausgabeform bringen. Das hast Du ja im Grunde schon so erkannt und getrennt in dem Du die Umwandlung in ein Tupel mit Tupeln mit Zahlen schon manuell gemacht hast. Wobei man da eher eine Liste mit Tupeln verwenden würde, denn die Werte bedeuten auf der Ebene ja alle das gleiche.

Code: Alles auswählen

#!/usr/bin/env python3

POLYGON_KEYWORD = "POLYGON"

POLYGON = "POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))"


def parse_braced(text):
    text = text.strip()
    if not (text.startswith("(") and text.endswith(")")):
        raise ValueError(f"expected braces, got {text!r} instead")
    return text[1:-1]


def parse_point(text):
    return tuple(map(int, parse_braced(text).split(None, 1)))


def parse_points(text):
    return map(parse_point, text.split(","))


def parse_polygon(text):
    keyword, points_text = text.strip().split(None, 1)
    if keyword != POLYGON_KEYWORD:
        raise ValueError(
            f"expected {POLYGON_KEYWORD}, got {keyword!r} instead"
        )
    return parse_points(parse_braced(points_text))


def main():
    points = parse_polygon(POLYGON)
    print("\n".join(f"P{i}=({x},{y})" for i, (x, y) in enumerate(points, 1)))


if __name__ == "__main__":
    main()
Das ist so geschrieben, dass es mit beliebig mehr „whitespace“ klar kommt, an den Stellen wo im Moment einzelne Leerzeichen stehen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Feedback1000: Alternativ könnte man da mit regulären Ausdrücken dran gehen (das externe `regex`-Modul ist kompatibel zum `re`-Modul aus der Standardbibliothek, kann aber noch ein bisschen mehr):

Code: Alles auswählen

#!/usr/bin/env python3
import regex as re

POLYGON_RE = re.compile(
    r"(?(DEFINE)(?P<point>\s*\(\s*(?P<x>\d+)\s+(?P<y>\d+)\s*\)))"
    r"\s*POLYGON\s+\((?&point)(\s*,(?&point))*\s*\)\s*"
)

POLYGON = "POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))"


def parse_polygon(text):
    data = POLYGON_RE.fullmatch(text).capturesdict()
    return zip(map(int, data["x"]), map(int, data["y"]))


def main():
    points = parse_polygon(POLYGON)
    print("\n".join(f"P{i}=({x},{y})" for i, (x, y) in enumerate(points, 1)))


if __name__ == "__main__":
    main()
Lesbarere Alternative wäre es sich einen Parser mit einer entsprechenden Bibliothek zu basteln:

Code: Alles auswählen

#!/usr/bin/env python3
import string

import pyparsing as pp

TEST_POLYGON = "POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))"

NUMBER = pp.Word(string.digits).add_parse_action(lambda tokens: int(tokens[0]))
PAIR = pp.Group(pp.Suppress("(") + NUMBER + NUMBER + pp.Suppress(")"))
PAIRS = pp.Suppress("(") + pp.delimited_list(PAIR) + pp.Suppress(")")
POLYGON = pp.Suppress(pp.Keyword("POLYGON")) + PAIRS + pp.string_end


def parse_polygon(text):
    return POLYGON.parse_string(text).as_list()


def main():
    points = parse_polygon(TEST_POLYGON)
    print("\n".join(f"P{i}=({x},{y})" for i, (x, y) in enumerate(points, 1)))


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

Hallo eure Ansätze klingen alle sehr vernünftig, aber momentan noch zu komplex zum einen für mich, aber vor allem auch für den momentanen Stand unserer Lehrveranstaltung.
Unterprogramme und das Einbinden von weiteren Bibliotheken hatten wir offiziell noch nicht (wird aber zum Glück demnächst eingeführt).
Für jetzt: wir sollen u.A. Dinge wie das folgende verwenden: https://docs.python.org/3/library/stdty ... ng-methods

So mein Stand der Dinge - auch wenn ich damit einigermaßen nicht zufrieden bin, da es noch zu unflexiebel ist (v. A. das letzte ")" bei "P10"):

Code: Alles auswählen

# V3

POLYGON = 'POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))'


STARTING_STRING ='('
SEPARATOR = ', '
STARTING_STRING = 'POLYGON '


position_1 = POLYGON.find(STARTING_DELIMITER, len(STARTING_STRING))
# print(position_1)

result = POLYGON[position_1+1:]
# print(result)

punkte = result.split(SEPARATOR)
print(punkte)

i = 1

for punkt in punkte:
    einzelwert = punkt.split()
    # print(einzelwert)
    rechtswert = einzelwert[0]
    # print(rechtswert)
    hochwert = einzelwert[1]
    # print(hochwert)
    print(f'P{i}={rechtswert},{hochwert}')
    i += 1
Langfristig werden wir Daten im WKT-Format (WKT = Well Known Text) (https://www.ibm.com/docs/de/db2/10.5?to ... esentation) aus einer ASCII auslesen und weiterverarbeiten.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

`split` kennst Du schon, und ich hatte aus gutem Grund geschrieben, dass man `find` besser nicht verwendet, weil es fehleranfällig ist.
Bei einem Parser muß man auch immer prüfen, ob die Daten, die man da bekommen hat auch dem entsprechen, was man erwartet.
`STARTING_DELIMITER` hast Du gar nicht definiert.
Du extrahierst die Zahlen gar nicht, sondern ignorierst, dass die Punkte in Klammern stehen. Deine Unzufriedenheit sollte also nicht nur die letzte Klammer sein, sondern auch alle anderen.

Erster Teil wäre also, den Typ abzuspalten:

Code: Alles auswählen

geotype, parameter = POLYGON.strip().split(None, 1)
if geotype != "POLYGON":
    raise ValueError("kein Polygon")
Dann prüft man, ob der Parameter Klammern hat:

Code: Alles auswählen

if not parameter.startswith('(') or not parameter.endswith(')'):
    raise ValueError("kein Klammern")
Denn nun kann man sie gefahrlos entfernen und am Komma aufspalten:

Code: Alles auswählen

points = parameter[1:-1].split(',')
Jetzt muß man natürlich noch für jeden Punkt prüfen, ob er geklammert ist und ob darin exakt zwei Zahlen stehen:

Code: Alles auswählen

for index, point in enumerate(points, 1):
    point = point.strip()
    if not point.startswith('(') or not point.endswith(')'):
        raise ValueError("kein Klammern")
    coordinates = list(map(float, point[1:-1].split()))
    if len(coordinates) != 2:
        raise ValueError("zwei Koordinaten erwartet")
    print(f'P{index}=({coordinates[0]},{coordinates[1]})')
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Feedback1000: Dann ist die Aufgabenstellung vielleicht schon zu komplex wenn nicht einmal Funktionen gegeben sind. Und spätestens wenn ein Format mit eine Spezifikation/Grammatik gegegeben ist, macht es deutlich mehr Sinn sich einen darauf aufbauenden Parser zu schreiben, oder auf fertige externe Bibliotheken zurück zu greifen, statt sich da was selbst zu basteln und das Rad damit neu zu erfinden, in der Regel nicht so schön rund wie es sein könnte/sollte.

„Unterprogramme“ ist ein Begriff der nicht so wirklich zu Funktionen und Python passt. Das ist so aus der Zeit wo man mit GOSUB zu nummerierten oder benannten Abschnitten gesprungen ist und auf lauter globalen Variablen operierte.

Man kann die Funktionen natürlich auch alle einsetzen, beziehungsweise auf die verzichten, die auf das vorhandensein von Klammern prüft und alles in die Hauptfunktion schreiben. Verzichtet dann halt auf die Prüfung auf Klammern, ist nicht wirklich sinnvoll testbar, und die Verständlichkeit leidet.

Code: Alles auswählen

#!/usr/bin/env python3

POLYGON_KEYWORD = "POLYGON"

POLYGON = "POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))"


def main():
    keyword, points_text = POLYGON.strip().split(None, 1)
    if keyword != POLYGON_KEYWORD:
        raise ValueError(
            f"expected {POLYGON_KEYWORD}, got {keyword!r} instead"
        )
    points = (
        tuple(map(int, point_text.strip()[1:-1].split(None, 1)))
        for point_text in points_text.strip()[1:-1].split(",")
    )

    print("\n".join(f"P{i}=({x},{y})" for i, (x, y) in enumerate(points, 1)))


if __name__ == "__main__":
    main()
Für WKT ist das aber auch falsch. Dort werden die Punkte anders dargestellt, und ein Polygon muss geschlossen sein, sonst wäre LINESTRING das passende Mittel.

Und bevor man sich da selbst was bastelt, würde man eher `shapely` verwenden:

Code: Alles auswählen

#!/usr/bin/env python3
import shapely

POLYGON = "POLYGON ((20 12, 10 11, 17 18, 20 20, 30 19, 29 18, 31 15, 28 10, 22 8, 19 9, 20 12))"


def main():
    points = shapely.from_wkt(POLYGON).boundary.coords
    print("\n".join(f"P{i}=({x},{y})" for i, (x, y) in enumerate(points, 1)))


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

@blackjack:
Deine erste Lösung fängt aber nicht ab, dass es auch float-Werte sein können...zumindest wirft es bei mir einen Fehler. Zum Beispiel:

Code: Alles auswählen

polygon = POLYGON ((20.8 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))
Die "Lösung" war - mit dem Hinweis, dass hier noch keine Formatprüfung auf "POLYGON " eingebaut wurde - folgendes:

Code: Alles auswählen

polygon = "POLYGON ((20 12), (10 11), (17 18), (20 20), (30 19), (29 18), (31 15), (28 10), (22 8), (19 9))"
#print(polygon[9:16])

startposition = polygon.find("(")
#print(startposition)

koordinaten = polygon[startposition+1:-1]
#print(koordinaten)

punkte_liste = koordinaten.split(", ")
#print(punkte_liste)

punktezaehler = 1
for punkt in punkte_liste:
    #print("Punkt:", punkt)
    mitte = punkt.find(" ")
    #print("Mitte:", mitte)
    x = punkt[1:mitte]
    #print("x-Wert:", x)
    y = punkt[mitte+1:-1]
    #print("y-Wert:", y)
    #print()
    # Format: P10=(19,9)
    print(f"P{punktezaehler}=({x},{y})")
    punktezaehler+=1
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Feedback1000: dass auch Punkte in den Zahlen vorkommen dürfen, wurde bisher noch nicht spezifiziert. Meine Lösung hat das einfach eigenmächtig verallgemeinert.
Dass das mit `find` die drittbeste Lösung ist, haben wir hier ja schon oft genug geschrieben. Aber wenn ihr eine BASIC statt Python lernen sollt, auch gut.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei das ja auch nicht so schwierig ist nachzurüsten.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten