suche Ausgabe: int(List[index_of_character_*])

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.
GabrielleChanel
User
Beiträge: 42
Registriert: Dienstag 13. April 2021, 11:54

@Sirius3 Oh danke, das ist eine super Idee das Rezept gleich in __init__ in die einzelnen Teile zu parsen, das macht alles viel überschaubarer und kann auch einfacher wieder verwendet werden, ohne dublizieren zu müssen.
Und ja genau, es geht darum die letzte Zeile mit einem * zu finden. Und sry, "ele" hatte ich als kürzere Form von "element" verwendet. Meine Version sieht ein bisschen "gebastelt" aus, ich habe join hinzugefügt, weil es alls Zeichen teilte, deswegen. Aber ich werde es wieder "entbasteln", sauberer machen.
Und danke, das mit dem zwei Mal speichern war mir vor unseren Diskussion gar nicht bewusst. Und super wegen "reversed" ich hatte .reverse() verwendet was nicht funktionierte und dann mit [::-1] ersetzt, aber stimmt, dass ist unnötig teuer. Danke dir. Und danke auch für deine Beispielcodes.
"Those who can imagine anything, can create the impossible." Alan Turing
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

IMHO noch besser das Parsen nicht in der `__init__()` zu machen sondern in einer Funktion oder Klassenmethode. Dann kann man `Recipe`-Objekte auch aus anderen Quellen erstellen und/oder für Tests einfach so direkt erstellen, ohne das man das parsen immer mittesten muss.

Beispiel:

Code: Alles auswählen

#!/usr/bin/env python3
import re
from datetime import timedelta as TimeDelta

from attr import attrib, attrs
from more_itertools import split_when


EXAMPLE_RECIPE = """\
Preparation time: 1 hour
*\t5\t\tcarrots
*\t1\tpinches\tsalt
Bake for 40 minutes.
"""

PREPARATION_TIME_RE = re.compile(r"Preparation time: (?P<hour>\d+) hour")


@attrs(frozen=True)
class Recipe:
    preparation_time = attrib()
    ingredients = attrib()
    instructions = attrib()

    @classmethod
    def parse(cls, text):
        (
            preparation_time_lines,
            ingredient_lines,
            instruction_lines,
        ) = split_when(
            text.splitlines(),
            lambda line_a, line_b: (
                line_a.startswith("*") + line_b.startswith("*") == 1
            ),
        )
        if len(preparation_time_lines) != 1:
            raise ValueError(
                f"more than one line for preparation_time:"
                f" {preparation_time_lines}"
            )

        match = PREPARATION_TIME_RE.fullmatch(preparation_time_lines[0])
        if not match:
            raise ValueError(
                f"preparation time has unexpected format:"
                f" {preparation_time_lines[0]!r}"
            )

        preparation_time = TimeDelta(hours=int(match.group("hour")))

        # ...

        return cls(preparation_time, ingredient_lines, instruction_lines)


def main():
    recipe = Recipe.parse(EXAMPLE_RECIPE)
    print(recipe)
    for line in recipe.instructions:
        print(line)


if __name__ == "__main__":
    main()
Das parsen ist wahrscheinlich noch nicht vollständig. Die Zubereitungszeit wird wahrscheinlich nicht nur in Stunden angegeben. Aus den Zutaten möchte man vielleicht die "*" entfernen den Rest vielleicht in Anzahl, Einheit, und Bezeichnung aufteilen und in einem eigenen Typ speichern. Und die Zubereitung vielleicht dann wieder als eine Zeichenkette, statt einzelnen Zeilen speichern.

Ausgabe von dem Beispiel:

Code: Alles auswählen

$ ./forum22.py 
Recipe(preparation_time=datetime.timedelta(0, 3600), ingredients=['*\t5\t\tcarrots', '*\t1\tpinches\tsalt'], instructions=['Bake for 40 minutes.'])
Bake for 40 minutes.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Ich frage mich die ganze Zeit warum ihr das nicht mit einer list comprehension macht.
Habe ich etwas missverstanden?
Es geht doch nur darum, das nächste Listenelement nach der letzten Zeile, die mit '*' beginnt auszugeben, oder?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@rogerb: Ich vermute die *eine* Zeile ist nur als Beispiel und die Anweisungen sind länger. Wie würdest Du das mit einer list comprehension machen? Die sind dazu da um Listen zu erstellen. Hier soll ja aber eine Liste eher zerlegt werden.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Wenn es darum geht eine Liste nach einfachen Regeln in eine neue Liste umzuwandeln, währe mein erster Ansatz immer eine list comprehension, (bzw. analog generator expression oder dictionary comprehension)

Code: Alles auswählen

lines_after_ingredients = next(recipe[index+1:] for index, (prev_line, next_line) in enumerate(zip(recipe[:-1], recipe[1:])) if prev_line.startswith("*") and not next_line.startswith("*"))
Das geht wahrscheinlich noch in vielen weiteren Varianten mit itertools, usw.

nach etwas Überlegung, würde ich die Daten aber wohl eher in einen Dictionary parsen:

Code: Alles auswählen

recipe_dict = {"remarks": [], "ingredients": [], "instructions": []}
Da hätte ich dann viele Möglichkeiten die Daten weiterzuverarbeiten.
Das in etwa soll wohl auch deine Klasse anbieten. Ich persönlich würde den dictionary aber aufgrund seiner Einfachheit vorziehen.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@rogerb: man muß nicht alles in eine List-Comprehension packen, das ist nur dann sinnvoll, wenn es sich wirklich um einfache Ausdrücke handelt.
Du packst aber den vorhergehende und die nachfolgende Zeile zusammen mit einem Index in einen Ausdruck, wo eine einfache for-Schleife reichen würde.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Sirius3,
ja die Lesbarkeit bei dieser list comprehension, stößt vielleicht schon an die Grenze. Ich persönlich arbeite sehr viel mit ...-comprehensions. Daher währe das mein erster Ansatz.
Aber es ist dann auch irgendwo Geschmacksache.

Wenn es gute Argumente für eine extra Klasse gibt, lasse ich mich gerne überzeugen. Bisher sehe ich die halt nicht.

Es ging mir hier auch nicht darum irgend eine Lösung abzuliefern, sondern eher um die Argumente für die eine oder die andere Methode.
GabrielleChanel
User
Beiträge: 42
Registriert: Dienstag 13. April 2021, 11:54

@__blackjack__ @Sirius3 @rogerb Vielen Dank euch dreien für die weiteren Inputs :) Und ja die Anweisungen im Programm sind viel länger als nur eine Zeile.
Zu deinem Kommentar @__blackjack__ ist es nun besser das "Parsen des Rezeptteile" vor dem __init__() zu machen (also so wie unten im Pseudocode)? Ich komme noch nicht ganz draus, was dann aus der __init__ Methode wird. Fällt sie weg oder was kommt da rein, dann? Also so stelle ich es mir nun vor:

Code: Alles auswählen

class...
    #part1 = des geparsten Rezepts
    #part2  = des geparste Rezepts
    #...
    def __init__(self):
        #? was kommt dann noch im init? Nichts mehr?
    def __iter__(self):
        #Inhalt: siehe vorherige Nachrichten
    def andere_funktionen(self):
        #...

def main():
    ...
    iter(a_receipe_name)
    ...
Und ja genau so sieht ein "verkürztes" example recipe aus, nur enthält sie noch weitere Zeilen wie beispielsweise eine Zeile Titel, eine Zeile Originaltitel, usw. und die hier 2. und 3. Zeile an Zutaten können beliebig viele sein. Die hier letzte Zeile, also Zeile 4, besteht wiederum aus mehreren beliebig vielen Anweisungen (hier auch nur eine Zeile zur Vereinfachung).
EXAMPLE_RECIPE = """\
Preparation time: 1 hour
*\t5\t\tcarrots
*\t1\tpinches\tsalt
Bake for 40 minutes.
"""
"Those who can imagine anything, can create the impossible." Alan Turing
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Also, wenn die __init__() leer ist, frage ich mich, warum du nicht einen generator verwendest. Was spricht dann noch für eine Klasse?

Wahlweise (sorry, muss ich einfach nochmal sagen) könnte man die Zeilen nach all den verschiedenen Kategorien wie Titel, Originaltitel, usw in einen dictionary vorsortieren.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@GabrielleChanel: __blackjack__ braucht kein __init__, weil er das attrs-Paket benutzt.
attrs erzeugt automatisch ein __init__, das so aussehen würde:

Code: Alles auswählen

class Recipe:
    def __init__(self, preparation_time, ingredients, instructions):
        self.preparation_time = preparation_time
        self.ingredients = ingredients
        self.instructions = instructions

    def iter_instructions(self):
        return iter(self.instructions)
GabrielleChanel
User
Beiträge: 42
Registriert: Dienstag 13. April 2021, 11:54

@Sirius3 Vielen herzlichen Dank, das wusste ich nicht mit den attr(), die selbst schon ein __init__() aufrufen. Stimmt, dann kann man diese ja weglassen, statt nochmals aufzuschreiben. Jetzt gibt alles Sinn, vielen Dank :)
"Those who can imagine anything, can create the impossible." Alan Turing
Antworten