Menue für die Tonne?

Code-Stücke können hier veröffentlicht werden.
Antworten
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo ich hatte aus gegebenem Anlaß (jemand hat im Java-Forum eine Menue-Steuerung ohne Swing vorgestellt und die war buggy und ich habe sie solange repariert, bis sie ging) mich an ein altes Programm aus dem letzten Jahrtausend erinnert, die auch sowas gemacht hat, Menue ohne alles dafür in dBaseIV :-) Der Vorteil meiner Lösung damals war, das Menue war über eine Datenbank konfigurierbar.
Ok Jahrzehnte später habe ich aus dem dBase Excel bzw CSV gemacht und bin mit Turbo-Pascal herangegangen. Dabei muß ich aber wohl einen kleinen Fehler eingebaut haben bei der Portierung oder es ging auch gar nicht so in Pascal wie in dBase. Also habe ich 2 Tage gesucht und nicht gefunden - das Programm läuft ja nur ist eine unerklärliche Ausnahmebehandlung drin, die das Auge für den Algorithmus mächtig stört.
Ich habe chatGPT gefragt, der hat es ehrlich gesagt nur verschlimmbessert, immerhin gewann ich so die Idee: weg von der Datei/CSV und lieber am Anfang einlesen in eine Liste.

Den hier gezeigten Code habe ich zu 60% selbst entworfen (vor allem den CSV-Reader etc). chatGPT hat dann die Ablauflogik ergänzt - teils freiwillig. teils nach Diskussion in deutscher Sprache und teils nach Vorlage interessanterweise lieber der dBase Quelle. An mein portiertes Pascal wollte er nicht ran bzw es kam nur Schrott dabei heraus. Nun würde ich gerne euer vernichtendes Urteil dazu hören

Code: Alles auswählen

import csv

def read_csv(filename):
    data = []
    with open(filename, newline='') as csvfile:
        reader = csv.DictReader(csvfile, delimiter=';')
        for row in reader:
            row['M1'] = int(row['M1'])
            row['M2'] = int(row['M2'])
            row['M3'] = int(row['M3'])
            row['N1'] = int(row['N1'])
            row['N2'] = int(row['N2'])
            row['L1'] = int(row['L1'])
            data.append(row)
    return data

def search_csv_2(data, m1, m2):
    result = []
    for row in data:
        if row['M1'] == m1 and row['M2'] == m2 and row['PROG'] != 'D':
            result.append((row['PROG'], row['BEZEICHNER'], row['CODE'], row['M3'], row['N1'], row['N2'], row['L1']))
    return result

def show_menu(data, m1, m2, bezeichnung="Hauptmenü"):
    menu_items = search_csv_2(data, m1, m2)

    # Wenn keine Menüeinträge vorhanden sind, sofort Fehlermeldung anzeigen
    if not menu_items:
        print("Ungültige Auswahl.")
        return menu_items

    # Andernfalls Menü anzeigen
    print(f"\n{bezeichnung}:")
    for prog, bezeichner, code, m3, n1, n2, l1 in menu_items:
        print(f"{m3}: {bezeichner}")
    
    print("0: Zurück")
    return menu_items

def find_menu_item(data, m1, m2, m3):
    for row in data:
        if row['M1'] == m1 and row['M2'] == m2 and row['M3'] == m3:
            return row
    return None

def run_menu(data):
    m1, m2 = 0, 0  # Start im Hauptmenü
    menu_stack = []  # Stack für die Menüs
    bezeichnung = "Hauptmenü"  # Start mit "Hauptmenü"
    menu_active = True
    menu_displayed = False  # Neu: Um zu steuern, ob das Menü erneut angezeigt wird

    while menu_active:
        if not menu_displayed:  # Nur wenn das Menü nicht bereits angezeigt wurde
            menu_items = show_menu(data, m1, m2, bezeichnung)
            menu_displayed = True  # Menü wurde jetzt angezeigt

        choice = int(input("Auswahl: "))

        if choice == 0:
            if not menu_stack:
                print("Programmende.")
                menu_active = False
            else:
                # Zurück ins übergeordnete Menü
                m1, m2, bezeichnung = menu_stack.pop()
                menu_displayed = False  # Menü muss wieder angezeigt werden
        else:
            selected_item = find_menu_item(data, m1, m2, choice)
            if selected_item:
                prog = selected_item['PROG']
                if prog.lower() == 'p':
                    print(f"Starte Programm: {selected_item['CODE']}")
                elif prog.lower() == 'm':
                    # Prüfe, ob das Untermenü gültige Einträge hat
                    n1, n2 = selected_item['N1'], selected_item['N2']
                    submenu_items = search_csv_2(data, n1, n2)
                    
                    if submenu_items:  # Nur wenn es gültige Einträge gibt
                        # Merke das aktuelle Menü
                        menu_stack.append((m1, m2, bezeichnung))
                        # Gehe ins Untermenü
                        m1, m2 = n1, n2
                        bezeichnung = selected_item['BEZEICHNER']  # Bezeichnung merken
                        menu_displayed = False  # Menü muss für das neue Untermenü angezeigt werden
                    else:
                        # Zeige Fehlermeldung, aber ändere M1 und M2 nicht
                        print("Ungültige Auswahl.")
            else:
                print("Ungültige Auswahl.")
                # Menü bleibt sichtbar, also bleibt menu_displayed = True unverändert

def main():
    csv_data = read_csv('freiburg.csv')
    run_menu(csv_data)

if __name__ == "__main__":
    main()
Ich habe die chatGPT Kommentare extra mal drin gelassen - ehrlich gesagt stören und verwirren die eher und ich weiß selbst daß die Struktur nicht gut ist. Meine dementsprechende Bitte um Modernisierung und Optimierung hat chatGPT zwar umgesetzt (Klassen, Enumeration, Annotationen und Logging), dabei aber leider eine falsche alte Version umgestellt, die dann wieder buggy war. Ich habe es 3x versucht, auch nur mit
Logging (das einzige was mich interessierte). Leider kann der sich nie die aktuelle fehlerfreie Version merken selbst wenn ich sie eingebe und greift immer wieder auf altes zurück und das hat mich am Ende so genervt, daß ich gesagt habe - okay Schluß damit, das ist ein Fall fürs Forum.

HIer übrigens meine gefakte EIngabedatei (die echte behandelt Fußball-Ligaverwaltung), und ja ich habe absichtlich Fehler eingebaut, damit alle Zweige auch duchlaufen werden.

Code: Alles auswählen

M1;M2;M3;PROG;BEZEICHNER;CODE;N1;N2;L1
0;0;1;P;Anwendung 1;Anw-1;99;99;0
0;0;2;P;Anwendung 2;Anw-2;99;99;0
0;0;4;M;Untermenue 1;99;0;4;1
0;0;5;M;Untermenue 2;99;0;5;0
0;0;9;D;(frei);0;0;9;0
0;6;1;M;Untermenue 1-1;99;4;1;1
0;6;2;M;Untermenue 1-2;99;4;2;0
0;6;3;P;Anwendung 1-3;Anw-1-3;99;99;0
0;5;1;M;Untermenue 2-1;99;5;1;1
0;5;2;M;Untermenue 2-2;99;5;2;0
0;5;3;P;Anwendung 2-3;Anw-2-3;99;99;0
4;1;1;P;Anwendung 1-1-1;Anw-1-1-1;99;99;0
4;1;2;P;Anwendung 1-1-2;Anw-1-1-2;99;99;0
5;1;1;P;Anwendung 2-1-1;Anw-2-1-1;99;99;0
5;1;2;P;Anwendung 2-1-2;Anw-2-1-2;99;99;0
4;2;1;P;Anwendung 1-2-1;Anw-1-2-1;99;99;0
4;2;2;P;Anwendung 1-2-2;Anw-1-2-2;99;99;0
5;3;1;P;Anwendung 2-2-1;Anw-2-2-1;99;99;0
5;3;2;P;Anwendung 2-2-2;Anw-2-2-2;99;99;0
Wie würde ich hier weiter vorgehen? Auf jeden Fall mal die störenden Kommentare rausschmeißen, dann ggf ein paar Variablen umbenennen und tatsächlich eine Klasse Menue-Kontroller erstellen (ohne die CSV-Lese Geschichte). Ich denke mal eine saubere Modularität sieht auch anders aus - nicht nur in Python, also an dem Spaghetti muß ich auch noch was machen, vielleicht reicht es ja da alles mal komplett auf einem Screen zu sehen.

Ich habe das aber etwas hinten an geschoben und mir lieber Gerüste (nur CSV Lesen/Auswerten und zwei Zugriffsfunktionen für das Array) in jeweils Pascal und Java erstellt und wollte nun mal sehen, wo ich die Logik besser hinbekomme. Natürlich weiß ich daß es nicht so schön wie in Python sein wird - aber okay werdet ihr sagen, in Python ist es ja auch alles andere als schön. Ich tppe mal am Ende auf die Gefallensreihenfolge
Python-Pascal-Java - kann mich aber irren.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wie weiter vorgehen? Wegwerfen, ChatGPT vergessen, und mal selber programmieren. In Python. Sinnvolle Namen wären nicht schlecht. Und eine Datenstruktur. Muss ja nicht objektorientiert sein, aber eine Klasse als Verbunddatentyp wäre schon nicht schlecht. In Java oder Pascal würde man das ja auch nicht in generische Datencontainer stecken, sondern sich eine Klasse oder Record schreiben. Mit ordentlichen Attributnamen.

Ansonsten fehlt mir da eine Beschreibung was das eigentlich tun soll, bzw. was die Daten bedeuten. Könnte man bei sinnvollen Namen sicher aus dem Code ableiten, aber so habe zumindest ich keine Lust da herumzuraten.
“I am Dyslexic of Borg, Your Ass will be Laminated” — unknown
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo, so hier nun mal der entzerrte Code. Ich habe es Freitag im Zug in Python neu entworfen, zu Pascal/Java hatte ich plötzlich keine Lust mehr, kommt vielleicht wieder.

Code: Alles auswählen

import csv
import logging 

logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')

def string_to_zahl(string):
    try:
        zahl = int(string)
    except ValueError:
        zahl = -1
    return zahl

def read_csv(filename):
    data = []
    with open(filename, newline='') as csvfile:
        reader = csv.DictReader(csvfile, delimiter=';')
        for row in reader:
            for auswahl in ['M1', 'M2', 'M3', 'N1', 'N2', 'L1']:
                row[auswahl] = string_to_zahl(row[auswahl])
            if row['PROG'] != 'D':
                data.append(row)
    return data

def menu_item_set(data, m1, m2):
    result = []
    for row in data:
        if row['M1'] == m1 and row['M2'] == m2:
            result.append(row)
    return result

def find_menu_item(data, m1, m2, m3):
    for row in data:
        if row['M1'] == m1 and row['M2'] == m2 and row['M3'] == m3:
            return row
    return None

def show_menu(data, m1, m2, bezeichner):
    menu_items = menu_item_set(data, m1, m2)
    if not menu_items:
        logging.error("Menüzeilen nicht vorhanden.")
    else:
        print(f"\n{bezeichner}:")
        for item in menu_items:
            print(f"{item['M3']}: {item['BEZEICHNER']}")
        print("0: Zurück")

def run_menu(data):
    m1, m2 = 0, 0
    menu_stack = []  
    bezeichnung = "Hauptmenü"  
    menu_displayed = False  

    while True:
        if not menu_displayed:
            show_menu(data, m1, m2, bezeichnung)
            menu_displayed = True  

        choice = int(input("Auswahl: "))
        if choice == 0:
            menu_displayed = False  
            if menu_stack:
                m1, m2, bezeichnung = menu_stack.pop()
            else:
                logging.info("Programmende.")
                return
        
        selected_item = find_menu_item(data, m1, m2, choice)
        if not selected_item:
            logging.warning("Ungültige Auswahl. Keine Einträge für das gewählte Menü.")
            continue

        prog, bezeichnung = selected_item['PROG'], selected_item['BEZEICHNER']  
        if prog.upper() == 'P':
            logging.info(f"Starte Programm: {selected_item['CODE']}")
        elif prog.upper() == 'M':
            n1, n2 = selected_item['N1'], selected_item['N2']
            if menu_item_set(data, n1, n2):
                menu_stack.append((m1, m2, bezeichnung))
                m1, m2 = n1, n2
                menu_displayed = False  
            else:
                logging.warning("Ungültige Auswahl. Aufgerufenes Menü nicht gefunden.")
        else:
            logging.error("Falscher Programmcode.")

def main():
    csv_data = read_csv('freiburg.csv')
    run_menu(csv_data)

if __name__ == "__main__":
    main()
Was soll gemacht werden. Ursprung war eine dBaseIV-Tabelle, die ich nun in eine CSV überführt habe (bzw neue Inhalte, das alte waren Fußball-Ligen)

Code: Alles auswählen

M1;M2;M3;PROG;BEZEICHNER;CODE;N1;N2;L1
0;0;1;P;Anwendung 1;Anw-1;99;99;0
0;0;2;P;Anwendung 2;Anw-2;99;99;0
0;0;4;M;Untermenue 1;99;0;4;1
0;0;5;M;Untermenue 2;99;0;5;0
0;0;9;D;(frei);0;0;9;0
0;4;1;M;Untermenue 1-1;99;4;1;1
0;4;2;M;Untermenue 1-2;99;4;2;0
0;4;3;P;Anwendung 1-3;Anw-1-3;99;99;0
0;5;1;M;Untermenue 2-1;99;5;1;1
0;5;2;M;Untermenue 2-2;99;5;2;0
0;5;3;P;Anwendung 2-3;Anw-2-3;99;99;0
4;1;1;P;Anwendung 1-1-1;Anw-1-1-1;99;99;0
4;1;2;P;Anwendung 1-1-2;Anw-1-1-2;99;99;0
5;1;1;P;Anwendung 2-1-1;Anw-2-1-1;99;99;0
5;1;2;P;Anwendung 2-1-2;Anw-2-1-2;99;99;0
4;2;1;P;Anwendung 1-2-1;Anw-1-2-1;99;99;0
4;2;2;P;Anwendung 1-2-2;Anw-1-2-2;99;99;0
5;2;1;P;Anwendung 2-2-1;Anw-2-2-1;99;99;0
5;2;2;P;Anwendung 2-2-2;Anw-2-2-2;99;99;0
oder mit Fehler, um das Programm zu testen

Code: Alles auswählen

M1;M2;M3;PROG;BEZEICHNER;CODE;N1;N2;L1
x;y;1;P;Anwendung 1;Anw-1;99;99;0
0;0;2;P;Anwendung 2;Anw-2;99;99;0
0;0;4;M;Untermenue 1;99;0;4;1
0;0;5;M;Untermenue 2;99;0;5;0
0;0;9;D;(frei);0;0;9;0
0;6;1;M;Untermenue 1-1;99;4;1;1
0;6;2;M;Untermenue 1-2;99;4;2;0
0;6;3;P;Anwendung 1-3;Anw-1-3;99;99;0
0;5;1;M;Untermenue 2-1;99;5;1;1
0;5;2;M;Untermenue 2-2;99;5;2;0
0;5;3;P;Anwendung 2-3;Anw-2-3;99;99;0
4;1;1;P;Anwendung 1-1-1;Anw-1-1-1;99;99;0
4;1;2;P;Anwendung 1-1-2;Anw-1-1-2;99;99;0
5;1;1;P;Anwendung 2-1-1;Anw-2-1-1;99;99;0
5;1;2;P;Anwendung 2-1-2;Anw-2-1-2;99;99;0
4;2;1;P;Anwendung 1-2-1;Anw-1-2-1;99;99;0
4;2;2;P;Anwendung 1-2-2;Anw-1-2-2;99;99;0
5;3;1;P;Anwendung 2-2-1;Anw-2-2-1;99;99;0
5;3;2;P;Anwendung 2-2-2;Anw-2-2-2;99;99;0

Ursprünglich konnte dBaseIV sich direkt daran abarbeiten (gibt sowas wie LOCATE), also in der Tabelle/"Datenbank" navigieren. Meine Umsetzung später in Pascal als Datei war eigentlich ein Rückschritt, weil ich ständig Open/Close bzw Reset gemacht habe. Nun also einlesen in ein Array (Pascal habe ich noch wieder den Sonderwunsch Turbo-Pascal 7 kompatibel also eher nichts dynamisches) und zwar haben die Zeilen folgende Bedeutung

M1, M2 sind der Menue-Level
M3 der eigentliche Auswahlpunkt

PROG kann M oder P sein:
bei M wird in das durch N1, N2 adressierte Menue gesprungen,
bei P das CODE genannte Modul aufgerufen

BEZEICHNER wird angezeigt im Menue, zusätzlich 0 für Zurück oder Ende.
L1 sind einfach Leerzeilen

Der letzte Teil der run_menue gefällt mir selbst auch nicht, vielleicht sollte das eine eigene Methode werden. Was meinst du mit Datenstruktur oder Klasse. Das sind doch meine csv_data. Meinst du die mit oberen 4 Methoden noch in eine Klasse kapseln?
Benutzeravatar
sparrow
User
Beiträge: 4501
Registriert: Freitag 17. April 2009, 10:28

Das fühlt sich an wie Technik aus den 80ern. Das würde man heute so nicht mehr tun.
Mag sein, dass man das in einer solchen Datenstruktur vorhalten kann, aber dann würde man die Struktur trotzdem anhand von Objekten darstellen.
Leider ist auch hier die Benennung super schlecht, und ich habe keine Lust mir Absätze durchzulesen um zu verstehen, was der "M1", "N1" oder "L1" bedeuten soll. Bennennung in Programmen ist wichtig. Das frisst kein Brot und Code wird nun mal deutlich öfter gelesen als geschrieben.

Nach meiner Erfahrung braucht ein simples Menü ~4 Informationen für einen Eintrag: id, parent_id, label und action.
Du hast hier irgendwie 9.
Sirius3
User
Beiträge: 18215
Registriert: Sonntag 21. Oktober 2012, 17:20

@alfware17: wenn Du Deine Input-Dateien nicht anpassen möchtest / kannst, dann heißt das aber noch nicht, dass auch Dein gesamtes Programm dieses komische Format nutzt. Der erste Schritt wäre, die Datei in eine passende Datenstruktur einzulesen, also eine zwei Klassen, eine für Untermenüpunkte, und eine für Programmmenüpunkte. Beide haben ja gemeinsame Attribute, wie Name, aber auch unterschiedliche, wie die Liste der Untermenüpunkte, bzw. dem Programmnamen.
Mit solch einer Datenstruktur wird das Programm dann auch deutlich lesbarer.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Da in den Daten jedes Menü unter verschiedenen Namen erreichbar sein könnte, habe ich mal drei Klassen daraus gemacht, beziehungsweise vier: eine Basisklasse mit den Gemeinsamkeiten der Menüeintragstypen:

Code: Alles auswählen

import csv
import logging
from collections import defaultdict

from attrs import field, frozen

logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")


@frozen
class Entry:
    id = field()
    description = field()

    def __str__(self):
        return f"{self.id}: {self.description}"


@frozen
class Program(Entry):
    code = field()

    def run(self):
        logging.info("Starte Programm: %r", self.code)
        return False


@frozen
class Menu(Entry):
    entries = field()

    def run(self):
        print(f"\n{self.description}:")
        return self.entries.run()


@frozen
class Entries:
    BACK_ID = 0
    
    _id_to_item = field(factory=dict)

    def __iter__(self):
        return iter(self._id_to_item.values())

    def add(self, entry):
        if entry.id == self.BACK_ID:  # Used for going back one menu level.
            raise ValueError(f"illegal entry id {self.BACK_ID!r}")

        if entry.id in self._id_to_item:
            raise ValueError(f"id {entry.id} already in entries")

        self._id_to_item[entry.id] = entry

    def run(self):
        while True:
            for entry in self:
                print(entry)
            print(f"{self.BACK_ID}: Zurück")

            while True:
                choice = int(input("Auswahl: "))
                if choice == self.BACK_ID:
                    return True

                entry = self._id_to_item.get(choice)
                if entry:
                    if entry.run():
                        break
                else:
                    logging.error("Falscher Programmcode.")


def read_csv(filename):
    id_to_entries = defaultdict(Entries)
    with open(filename, encoding="ascii", newline="") as csvfile:
        for row_number, row in enumerate(
            csv.DictReader(csvfile, delimiter=";"), 1
        ):
            typecode = row["PROG"]
            if typecode != "D":  # Not deleted.
                for integer_column_name in [
                    "M1",
                    "M2",
                    "M3",
                    "N1",
                    "N2",
                    "L1",
                ]:
                    row[integer_column_name] = int(row[integer_column_name])

                entries_id = (row["M1"], row["M2"])
                entry_id = row["M3"]
                description = row["BEZEICHNER"]

                entry = None
                if typecode == "M":
                    entry = Menu(
                        entry_id,
                        description,
                        id_to_entries[row["N1"], row["N2"]],
                    )
                elif typecode == "P":
                    entry = Program(entry_id, description, row["CODE"])
                else:
                    logging.warning(
                        "unexpected typecode %r in row %d",
                        typecode,
                        row_number,
                    )

                if entry is not None:
                    id_to_entries[entries_id].add(entry)

    return Menu(None, "Hauptmenü", id_to_entries[(0, 0)])


def main():
    read_csv("test.csv").run()
    logging.info("Programende.")


if __name__ == "__main__":
    main()
“I am Dyslexic of Borg, Your Ass will be Laminated” — unknown
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo Sirius3 und _blackjack_ Danke für eure wertvollen Ideen. Ich kann und möchte nur lernen dabei. Den Code von _blackjack_ werde ich morgen mir zu Gemüte ziehen, sicher ist er viel eleganter als meiner, den ich heute nachmittag gemacht habe. Ich wollte auch ein bißchen objektorientiert machen und letztlich ist folgendes dabei heraus gekommen:

Code: Alles auswählen

import csv
import logging

logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')

class MenueZeile:
    def __init__(self, m1, m2, m3, prog, bezeichner, code, n1, n2, l1):
        self.m1 = m1
        self.m2 = m2
        self.m3 = m3
        self.prog = prog
        self.bezeichner = bezeichner
        self.code = code
        self.n1 = n1
        self.n2 = n2
        self.l1 = l1

class MenueManager:
    def __init__(self, dateiname):
        self.daten = self.csv_lesen(dateiname)
        self.m1 = 0
        self.m2 = 0
        self.menue_stapel = []  
        self.bezeichnung = "Hauptmenü"
        self.menue_angezeigt = False

    def aktuelle_menuepunkte(self):
        return [zeile for zeile in self.daten if zeile.m1 == self.m1 and zeile.m2 == self.m2]

    def finde_menuepunkt(self, auswahl):
        return next((zeile for zeile in self.daten if zeile.m1 == self.m1 and zeile.m2 == self.m2 and zeile.m3 == auswahl), None)

    def zeige_menue(self):
        menuepunkte = self.aktuelle_menuepunkte()
        if not menuepunkte:
            logging.error("Menüzeilen nicht vorhanden.")
        else:
            print(f"\n{self.bezeichnung}:")
            for punkt in menuepunkte:
                print(f"{punkt.m3}: {punkt.bezeichner}")
                for i in range(punkt.l1):
                    print()
            print("\n0: Zurück")

    def hat_nachfolge_menue(self, n1, n2):
        return any(zeile for zeile in self.daten if zeile.m1 == n1 and zeile.m2 == n2)

    def bearbeite_auswahl(self, auswahl):
        if auswahl == 0:
            self.menue_angezeigt = False
            if self.menue_stapel:
                self.m1, self.m2, self.bezeichnung = self.menue_stapel.pop()
                return True
            else:
                logging.info("Programmende.")
                return False

        punkt = self.finde_menuepunkt(auswahl)
        if not punkt:
            logging.warning("Ungültige Auswahl. Keine Einträge für das gewählte Menü.")
            return True
            
        self.bezeichnung = punkt.bezeichner
        if punkt.prog.upper() == 'P':
            logging.info(f"Starte Programm: {punkt.code}")
  
        elif punkt.prog.upper() == 'M':
            if self.hat_nachfolge_menue(punkt.n1, punkt.n2):
                self.menue_stapel.append((self.m1, self.m2, self.bezeichnung))
                self.m1, self.m2 = punkt.n1, punkt.n2
                self.menue_angezeigt = False
            else:
                logging.warning("Ungültige Auswahl. Aufgerufenes Menü nicht gefunden.")
        else:
            logging.error("Falscher Programmcode.")
        
        return True

    def fuehre_menue_aus(self):
        while True:
            if not self.menue_angezeigt:
                self.zeige_menue()
                self.menue_angezeigt = True
            try:
                auswahl = int(input("Auswahl: "))
                if not self.bearbeite_auswahl(auswahl):
                    break
            except ValueError:
                logging.error("Ungültige Eingabe. Bitte geben Sie eine Zahl ein.")

    def csv_lesen(self, dateiname):
        def str2int(string):
            try:
                return int(string)
            except ValueError:
                return -1
        daten = []
        with open(dateiname, newline='') as csvfile:
            for zeile in csv.DictReader(csvfile, delimiter=';'):
                if zeile['PROG'] != 'D':
                    daten.append(MenueZeile(
                        str2int(zeile['M1']),
                        str2int(zeile['M2']),
                        str2int(zeile['M3']),
                        zeile['PROG'],
                        zeile['BEZEICHNER'],
                        zeile['CODE'],
                        str2int(zeile['N1']),
                        str2int(zeile['N2']),
                        str2int(zeile['L1'])
                    ))
        return daten

def main():
    menue_manager = MenueManager('freiburg.csv')
    menue_manager.fuehre_menue_aus()

if __name__ == "__main__":
    main()
Ich glaube bißchen besser als der dBaseIV-Code sieht es schon aus (wenn auch länger), dafür kann er immer noch kein echtes execute, was dBase schon konnte und _blackjack_ auch, wenn ich es richtig verstehe.
nezzcarth
User
Beiträge: 1733
Registriert: Samstag 16. April 2011, 12:47

Ein Menü ist doch ein gerichteter Graph bzw. ein Baum, wenn jeder Menüpunkt genau einen Elternknoten und das Menü keine Schleifen hat. Neben den gezeigten pythonischeren Ansätzen gehen daher natürlich auch die üblichen Implementierungsansätze für diese Datenstrukturen. Da du Pascal und Java erwähnt hattest, hatte ich erwartet, dass du dort mit Zeigern und Records bzw. Klassen und Objektreferenzen in deinem Code gearbeitet hast; letzteres würde ja auch in Python gehen. Die Konstruktion mit dem Array und den drei Ms erscheint mir etwas kompliziert.
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hi, ja das kann schon sein, daß es (viel) bessere Ansätze für ein Menü gibt. Was ich alleine heute in kurzer Zeit mit tkinter gesehen habe - aber alles neu für mich.

Zurück zum Ausgangspunkt. Es war ein dBaseIV-Programm mit einer Tabelle/DB und der Algorithmus lief recht gut. Die auszuführenden "Programme" waren übrigens wieder andere dBaseIV-Module bzw nur ein einziges aber mit jeweils 153 verschiedenen Parameterdateien (Feld Code). Ich habe das aus Neugier umgeschrieben nach Turbo-Pascal und dabei a) irgendeinen Fehler gemacht, der immer noch drin ist und b) in Kauf genommen: sequentielle Textdatei und c) nicht mal ein execute drin.

Neulich habe ich in Java Menues gemacht, mit und ohne Swing und mich der Sache mit der dBase-Tabelle erinnert. Ich hatte spekuliert, daß mir bzw euch bzw ChatGPT die Umsetzung der CSV-Geschichte in Python leichter fällt als in Pascal oder Java und dachte das bißchen Variablenlogik kann so schlimm nicht sein. Nun ist es also objektorientiert
und ich habe sehr viel dabei gelernt. Und danke an @ _blackjack_ habe deine Lösung probiert nur nicht vollständig verstanden mit den Objekten und dem run(). Aber man sieht den python-qualitativen Unterschied zu meinem.

Wahrscheinlich wird es mir nun leichter fallen, meine letze Lösung nach Java und Pascal umzuschreiben - brauchen tue ich das nicht, nur aus Spaß und Interesse.

Zur Frage Zeiger und OO in Java/Pascal - jein, ich wollte eigentlich beim Array/Listen Ansatz bleiben und muß das dynamische oder die vorhandenen GUIs nicht neu erfinden,
mir geht/ging es nur um die 1:1 Umsetzung meines Algorithmus und natürlich um den Bug/Schönheitsfehler im Pascal Programm. Eher würde ich noch mal drüber nachdenken,
die Datenbank von key M1+M2+M3 auf noch 1 oder 2 Ebenen also M4+M5 zu erweitern, natürlich habe ich keine Testdaten für so eine Monster Hierarchie von Menueebenen.
nezzcarth
User
Beiträge: 1733
Registriert: Samstag 16. April 2011, 12:47

Vielleicht habe ich es nicht richtig verstanden, was du vorhast, aber eigentlich ist doch relativ egal, aus welcher Quelle (Excel, CSV, dBase oder etwas das Schachtelung erlaubt wie Toml oder JSON) das Menü jetzt kommt. Das sollte doch so abstrahiert sein, dass es keine Rolle spielt und idealerweise kann man das Menü ja vielleicht später sogar in diverse Formate serialisieren und daraus lesen. Wenn das händisch befüllt werden soll, ist das in CSV erforderliche Jonglieren mit IDs – in deinem Fall ja sogar mehreren – jedenfalls ja eher fehleranfällig, wie ja selbst schon implizit gezeigt hast.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das ganze mal in Pascal. Das parsen des CSV ist gruselig und sehr beschränkt auf das was in der Datei stehen *sollte* mit wenig bis gar keiner Fehlerprüfung was das Format angeht. Und sich um die Speicherverwaltung zu kümmern kostete Programmierzeit und Nerven. 😎

Code: Alles auswählen

type
  PEntry = ^TEntry;
  TEntry = object
    id: Char;
    description: string;
    next: PEntry;
    constructor Init(anId: Char; const aDescription: string);
    destructor Done; virtual;
    procedure Write;
    function Run: Boolean; virtual;
  end;
  PEntries = ^TEntries;
  TEntries = object
    first: PEntry;
    last: PEntry;
    constructor Init;
    destructor Done;
    procedure Add(entry: PEntry);
    function Get(id: Char): PEntry;
    procedure Write;
    function Run: Boolean;
  end;
  PProgram = ^TProgram;
  TProgram = object(TEntry)
    code: string;
    constructor Init(anId: Char; const aDescription: string;
                     const aCode: string);
    function Run: Boolean; virtual;
  end;
  PMenu = ^TMenu;
  TMenu = object(TEntry)
    entries: PEntries;
    constructor Init(anId: Char; const aDescription: string;
                     theEntries: PEntries);
    destructor Done; virtual;
    function Run: Boolean; virtual;
  end;

var
  menu: PMenu;

constructor TEntry.Init(anId: Char; const aDescription: string);
begin
  id := anId;
  description := aDescription;
  next := nil;
end;

destructor TEntry.Done;
begin
end;

procedure TEntry.Write;
begin
  WriteLn(id, ': ', description);
end;

function TEntry.Run: Boolean;
begin
  Run := FALSE;
end;

constructor TEntries.Init;
begin
  first := nil;
  last := nil;
end;

destructor TEntries.Done;
var
  entry, next: PEntry;
begin
  entry := first;
  while Assigned(entry) do
    begin
      next := entry^.next;
      Dispose(entry, Done);
      entry := next;
    end;
  Init;
end;

procedure TEntries.Add(entry: PEntry);
begin
  if Assigned(first) then
    begin
      last^.next := entry;
      last := last^.next
    end
  else
    begin
      first := entry;
      last := entry;
    end;
end;

procedure TEntries.Write;
var
  entry: PEntry;
begin
  entry := first;
  while Assigned(entry) do
    begin
      entry^.Write;
      entry := entry^.next;
    end;
end;

function TEntries.Get(id: Char): PEntry;
var
  result: PEntry;
begin
  result := first;
  while Assigned(result) do
    if result^.id = id then Break else result := result^.next;
  Get := result;
end;

function TEntries.Run: Boolean;
var
  finished: Boolean;
  c: Char;
  entry: PEntry;
begin
  finished := FALSE;
  repeat
    Write;
    WriteLn('0: Zurück');
    while True do
      begin
        Read(c);
        if not (c in [#13, #10]) then
          begin
            if c = '0' then
              begin
                Run := TRUE;
                finished := TRUE;
                Break;
              end;

            entry := Get(c);
            if Assigned(entry) then
              begin
                if entry^.Run then Break;
              end
            else
              WriteLn('Falsche Eingabe!');
          end;
      end;
  until finished;
end;

constructor TProgram.Init(anId: Char; const aDescription: string;
                          const aCode: string);
begin
  inherited Init(anId, aDescription);
  code := aCode;
end;

function TProgram.Run: Boolean;
begin
  WriteLn('Starte Programm: ', code);
  Run := FALSE;
end;

constructor TMenu.Init(anId: Char; const aDescription: string;
                       theEntries: PEntries);
begin
  inherited Init(anId, aDescription);
  entries := theEntries;
end;

destructor TMenu.Done;
begin
  Dispose(entries, Done);
end;

function TMenu.Run: Boolean;
begin
  WriteLn;
  WriteLn(description, ':');
  Run := entries^.Run;
end;

function ReadMenu(const filename: string): PMenu;
type
  TId = string[2];
  PItem = ^TItem;
  TItem = record
    id: TId;
    entries: PEntries;
    isReferenced: Boolean;
    next: PItem;
  end;
var
  f: Text;
  line: string;
  i, j: Byte;
  columnEnds: array[1..8] of Byte;
  typeCode: Char;
  entryId: Char;
  description: string;
  entry: PEntry;
  idToEntries: PItem;

  function getId(column: Byte): TId;
  begin
    getId := line[columnEnds[column] - 1] + line[columnEnds[column + 1] - 1];
  end;

  function getColumn(column: Byte): string;
  var
    i, j: Byte;
  begin
    i := columnEnds[column - 1] + 1;
    j := columnEnds[column];
    getColumn := Copy(line, i, j - i);
  end;

  function getEntries(const id: TId; isReferenced: Boolean): PEntries;
  var
    found: Boolean;
    result: PItem;
  begin
    found := FALSE;
    result := idToEntries;
    while Assigned(result) and not found do
      if result^.id = id then found := TRUE else result := result^.next;
    if not found then
      begin
        New(result);
        result^.id := id;
        New(result^.entries, Init);
        result^.isReferenced := FALSE;
        result^.next := idToEntries;
        idToEntries := result;
      end;
    result^.isReferenced := result^.isReferenced or isReferenced;
    getEntries := result^.entries;
  end;

  procedure disposeIdToEntries;
  var
    next: PItem;
  begin
    while Assigned(idToEntries) do
      begin
        next := idToEntries^.next;
        if not idToEntries^.isReferenced then
          Dispose(idToEntries^.entries, Done);
        Dispose(idToEntries);
        idToEntries := next;
      end;
  end;

begin
  idToEntries := nil;
  Assign(f, filename);
  Reset(f);
  ReadLn(f, line);  {Skip header.}
  while not Eof(f) do
    begin
      ReadLn(f, line);

      {Get positions of ';' in line.}
      for i := 1 to 8 do columnEnds[i] := Length(line);
      j := 1;
      for i := 1 to Length(line) - 1 do
        begin
          if (line[i] = ';') and (j <= 8) then
            begin
              columnEnds[j] := i;
              Inc(j);
            end;
        end;

      typeCode := line[columnEnds[4] - 1];

      {Process non-deleted records.}
      if typeCode <> 'D' then
        begin
          entryId := line[columnEnds[3] - 1];
          description := getColumn(5);
          entry := nil;
          case typeCode of
            'P':
              entry := New(PProgram,
                           Init(entryId, description, getColumn(6)));
            'M':
              entry := New(PMenu,
                           Init(entryId, description,
                                getEntries(getId(7), TRUE)));
            else
              begin
                WriteLn('Fehler Unbekannter Datensatztyp "', typeCode, '"!');
                Close(f);
                Halt(1);
              end;
          end;
          getEntries(getId(1), FALSE)^.Add(entry);
        end;
    end;
  Close(f);
  ReadMenu := New(PMenu, Init(#0, 'Hauptmenü', getEntries('00', TRUE)));
  disposeIdToEntries;
end;

begin
  menu := ReadMenu('test.csv');
  menu^.Run;
  Dispose(menu, Done);
  WriteLn('Programmende.');
end.
Edit: Wobei das ein mögliches Speicherleck hat. Nicht bei den Beispieldaten, aber die Prüfung ob Menüeinträge tatsächlich referenziert und benutzt werden ist zu grosszügig, so das welche am Leben bleiben könnten, die nicht vom Hauptmenü aus erreichbar sind. ☹️
“I am Dyslexic of Borg, Your Ass will be Laminated” — unknown
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Danke für deinen Pascal-Code - das kannst du also auch... :-) Ich werde es ausprobieren, ich fürchte jedoch ich werde wegen der Anforderung, daß es in Turbo-Pascal laufen soll (Zielvorgabe), auf Objekte und Spielchen verzichten muß. Mein Parsen funktioniert übrigens schon ganz gut über csvStrip, na gut Fehlerbehandlung kann ich ja noch hinzufügen. Obwohl dies ein Python Forum ist, habe ich mich mal entschlossen, das mittlerweile fast 30 Jahre alte Pascal Unglückswerk hier zu posten.

Code: Alles auswählen

PROGRAM freiburg;
USES crt;
VAR zeile,wort,code,bezeichnung:STRING;
    datei:TEXT;
    gefunden,ende,anzeige,neueseite:BOOLEAN;
    prog,ch:CHAR;
    cc,w,lz,l1,ee,znr,nr,b1,b2,b3,m1,m2,m3,n1,n2,a3:INTEGER;
FUNCTION Strip_CSV(VAR zeile:STRING):STRING;
VAR p:INTEGER; t:STRING;
BEGIN
   t:=''; p:=POS(';',zeile);
   IF p>0 THEN BEGIN
      t:=COPY(zeile,1,p-1);
      zeile:=COPY(zeile,p+1,255);
   END;
   Strip_CSV:=t;
END;
PROCEDURE Lesen2;
BEGIN
   REPEAT
      ReadLn(datei,zeile);
   UNTIL (COPY(zeile,1,3)<>'M1;') AND (COPY(zeile,1,3)<>';;;')
END;
PROCEDURE Lesen;
BEGIN
   Lesen2;
   wort:=Strip_CSV(zeile); Val(wort,w,cc); IF cc=0 THEN m1:=w;
   wort:=Strip_CSV(zeile); Val(wort,w,cc); IF cc=0 THEN m2:=w;
   wort:=Strip_CSV(zeile); Val(wort,w,cc); IF cc=0 THEN m3:=w;
   wort:=Strip_CSV(zeile); prog:=wort[1];
   bezeichnung:=Strip_CSV(zeile);
   code:=Strip_CSV(zeile);
   wort:=Strip_CSV(zeile); Val(wort,w,cc); IF cc=0 THEN n1:=w;
   wort:=Strip_CSV(zeile); Val(wort,w,cc); IF cc=0 THEN n2:=w;
   wort:=Strip_CSV(zeile); Val(wort,w,cc); IF cc=0 THEN l1:=w;
END;
PROCEDURE Eingeben;
BEGIN
   REPEAT
      WriteLn; Write('   0 bis ',nr,': ');
      REPEAT UNTIL KEYPRESSED; ch:=READKEY;
      Val(ch,w,cc); IF cc=0 THEN ee:=w;
      WriteLn(ch);
   UNTIL ee IN [0..nr];
   IF (ee=0) AND (b1=0) AND (b2=0) THEN ende:=TRUE;
END;
PROCEDURE Ausgeben;
BEGIN
   INC(nr); INC(znr);
   IF anzeige THEN BEGIN
      IF neueseite THEN BEGIN; neueseite:=FALSE; ClrScr; END;
      IF prog<>'D' THEN WriteLn(znr:2,':',m3,' ',bezeichnung) ELSE WriteLN;
      FOR lz:=1 TO l1 DO WriteLn; znr:=znr+lz; lz:=0;
   END;
END;
PROCEDURE Auswerten;
BEGIN
   IF ende THEN BEGIN {WriteLn('Ende');} EXIT; END;
   IF NOT gefunden THEN BEGIN
      IF ee=0
         THEN BEGIN
            {WriteLn('eine Stufe zurck');}
            anzeige:=TRUE; neueseite:=TRUE;
            IF b1=0
               THEN BEGIN
                  {WriteLn('von B3 auf B2 weil B1 schon 0');}
                  b3:=b2; b2:=0;
               END
               ELSE BEGIN
                  {WriteLn('von B2 auf B1 weil B1 > 0');}
                  b2:=b1; b1:=0;
               END
         END
         {ELSE WriteLn('Steuerfehler???')};
      EXIT;
   END;
   {WriteLn('Gefunden');}
   CASE prog OF
   'D','d': BEGIN
               {WriteLn('Dummy');}
            END;
   'M','m': BEGIN
               {WriteLn('neues Menue: n1=',n1,' n2=',n2);}
               b1:=n1; b2:=n2; anzeige:=TRUE; neueseite:=TRUE;
            END;
   'P','p': BEGIN
               WriteLn;
               WriteLn('-> Programmaufruf:',code); anzeige:=TRUE; neueseite:=TRUE;
               REPEAT UNTIL KEYPRESSED; ch:=READKEY;
            END;
   END;
END;
BEGIN
   b1:=0; b2:=0; b3:=0; m1:=0; m2:=0; m3:=0; n1:=0; n2:=0; l1:=0;
   bezeichnung:=''; prog:=' '; code:=''; ende:=FALSE;
   anzeige:=TRUE; neueseite:=TRUE;
   Assign(datei,'freiburg.csv');
   {WriteLn('Start');}
   WHILE NOT ende DO BEGIN;
      {$I-} Reset(datei); {$I+} IF IOResult<>0 THEN Halt;
       nr:=0; znr:=0; ee:=0; gefunden:=FALSE;
       {šberlesen der 1.Zeile ReadLn(datei);}
       {WriteLn('Durchsuchen 1');}
       WHILE NOT EOF(datei) DO BEGIN;
          Lesen;
          {a3 hat mit dem Algorithmus von dBase nichts zu tun
           hier liegt nur ein "Steuerfehler" fr den letzten XLS-Satz vor}
          IF (b1=m1) AND (b2=m2) AND (a3<>m3) THEN Ausgeben;
          a3:=m3;
       END;
       {WriteLn('Eingabe');}
       WriteLn; WriteLn('   0 Ende');
       anzeige:=FALSE;
       Eingeben;
       IF NOT ende THEN BEGIN
          {$I-} Reset(datei); {$I+} IF IOResult<>0 THEN Halt;
          {WriteLn('Durchsuchen 2');}
          WHILE (NOT EOF(datei)) AND (NOT gefunden) DO BEGIN;
             Lesen;
             IF (b1=m1) AND (b2=m2) AND (ee=m3) THEN gefunden:=TRUE;
          END;
       END;
       {WriteLn('Auswerten');}
       Auswerten;
   END;
   {WriteLn('Ende');}
   {REPEAT UNTIL KEYPRESSED; ch:=READKEY;}
END.
Das mit der sequentiellen Textdatei muß natürlich raus - ich habe es ehrlich gesagt damals 1:1 umgesetzt aus der dBaseIV-Quelle und die Idee einer Liste bzw Array die am Anfang eingelesen werden, kam mir erst jetzt wo ich sah was das csv-Modul so schönes kann.

Hier zur Abschreckung gleich noch das noch ein wenig ältere dBaseIV-Original:

Code: Alles auswählen

SET BELL OFF
SET TALK OFF
SET CATALOG OFF
SET STATUS OFF
SET SAFETY OFF
PUBL ARRAY fd_ein [10]
PUBL fd_b1,fd_b2,fd_b3,fd_l1
PUBL fd_ende,fd_nr,fd_znr,fd_ee
PUBL fd_anz,fd_neue,fd_fund
PUBL fd_befehl,befehl
fd_befehl='DO FREISUB WITH "'
fd_b1=0
fd_b2=0
fd_b3=0
fd_l1=0
fd_ende=.F.
fd_anz=.T.
fd_neue=.T.
fd_fund=.F.
USE FYDORG
DO WHILE .NOT.fd_ende
  CLEAR
  CLEAR GETS
  @  1,0 TO 22,79 DOUBLE
  @  2,1 TO 4,78 DOUBLE
  @  3,10 SAY "MENUE DEMO"
  fd_znr=6
  fd_nr=0
  fd_ee=0
  fd_fund=.F.
  i=0
  DO WHILE i<10
    fd_ein[i+1]=""
    i=i+1
  ENDDO
  GO TOP
  LOCATE FOR (fd_b1=m1).AND.(fd_b2=m2)
  DO WHILE .NOT.EOF()   
    fd_znr=fd_znr+1
    IF fd_anz
      IF fd_neue
	fd_neue=.F.
      ENDIF
      IF prog<>'D'
	@ fd_znr,19 SAY STR(m3,3)
	@ fd_znr,24 SAY bezeichner
	fd_ein[fd_nr+1]="*"
      ENDIF
      fd_znr=fd_znr+l1
      fd_nr=fd_nr+1
    ENDIF
    CONTINUE
  ENDDO
  @ 20,21 SAY '0. ENDE'
  @ 22,33 SAY " Auswahl    "
  DO WHILE .T.
    @ 22,42 GET fd_ee PICT "9" RANGE 0,fd_nr
    READ
    IF fd_ee=0
      EXIT
    ENDIF
    IF fd_ein[fd_ee]="*"
      EXIT
    ENDIF
  ENDDO
  IF (fd_ee=0).AND.(fd_b1=0).AND.(fd_b2=0)
    fd_ende=.T.
  ENDIF
  fd_anz=.F.
  IF .NOT.fd_ende
    GO TOP 
    LOCATE FOR (fd_b1=m1).AND.(fd_b2=m2).AND.(fd_ee=m3)
    IF .NOT.EOF()
      fd_fund=.T.
    ENDIF
    IF .NOT.fd_fund
      IF fd_ee=0
	fd_anz=.T.
	fd_neue=.T.
	IF fd_b1=0
	  fd_b3=fd_b2
	  fd_b2=0
	ELSE
	  fd_b2=fd_b1
	  fd_b1=0
	ENDIF
      ENDIF
    ELSE
      DO CASE
	CASE (prog='D').OR.(prog='d')
	CASE (prog='M').OR.(prog='m')
	  fd_b1=n1
	  fd_b2=n2
	  fd_anz=.T.
	  fd_neue=.T.
	CASE (prog='P').OR.(prog='p')
	  SAVE TO "FREIBURG.MEM" ALL LIKE FD*
	  befehl=fd_befehl+code+'.MEM"'
	  &befehl
	  RESTORE FROM "FREIBURG.MEM" ADDI
	  USE FYDORG
	  GO TOP
	  fd_anz=.T.
	  fd_neue=.T.         
      ENDCASE  
    ENDIF
  ENDIF
ENDDO
SET BELL ON
SET TALK ON
CLEAR ALL
RETURN
Das "ausgeführte" Programm (&befehl) sieht dann übrigens so aus:

Code: Alles auswählen

PARAMETERS PFILE
SET STATUS OFF
SET CATALOG OFF
SET TALK OFF
SET SAFETY OFF
SET STEP OFF
SET BELL OFF
SET SCORE OFF
SET ESCAPE OFF
SET CONFIRM OFF
SET DECIMALS TO 3

***********************************************************************
f4_event="Deutsche Meisterschaft  Fuáball-Bundesliga"
f4_anz_mann=     18
f4_anz_auf=       5
f4_anz_ab=        3
f4_anz_show1=     9
f4_straf_flag=   .F.
f4_dbspiele=    "SPIELE"
f4_dbliga=      "LIGA"
f4_anz_gew=       3
f4_anz_un=        1
f4_list_aus=      1 
f4_x_filename=  "LISTEN"
f4_tor_quotient= .F.
***********************************************************************

BEFEHL="REST FROM "+PFILE+" ADDITIVE"
FNAME="('"+PFILE+".MEM')"
IF FILE &FNAME
  &BEFEHL
ENDIF

disp memo

set talk on

clear all

wait

disp memo

set talk off

Wobei der Inhalt von FREISUB im Original mein eigentliches dBaseIV-Programm zur Eingabe/Verwaltung der Ligaergebnisse war (hier nur symbolisch disp memo). Die Tabelle FYDORG entsprach der CSV-Datei, ich überführe sie übrigens nach wie vor ineinander über den Umweg Excel, wo ich das CSV pflege und in den DBASE-DBF sind dann die eigentlichen Fußball Daten.
Ja ich weiß, wenn ich mal viel Zeit habe, schreibe ich das neu in Python oder ich nutze einfach eine Lösung aus dem Internet mit Datenupdate
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@alfware17: OBJECT gibt es seit Turbo Pascal 5.5. Geschrieben habe ich das Pascal-Programm in Turbo Pascal 7 unter DOS.
“I am Dyslexic of Borg, Your Ass will be Laminated” — unknown
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

@_blackjack_ oh danke, das habe ich noch nie probiert. Werde ich aber gleich mal hiermit nachholen. Wenn Java fertig, gehts weiter

Code: Alles auswählen

PROGRAM csvread_obj;

TYPE
   PSatz = ^TSatz;
   TSatz = OBJECT
      M1, M2, M3, N1, N2, L1: INTEGER;
      PROG, BEZEICHNER, CODE: STRING[50];
      Folge: PSatz;

      PROCEDURE Init(M1_, M2_, M3_ : INTEGER; PROG_, BEZEICHNER_, CODE_: STRING; N1_, N2_, L1_: INTEGER);
      PROCEDURE Zeigen;
   END;

   TDaten = OBJECT
      Kopf, Fuss: PSatz;
      Anzahl: INTEGER;

      PROCEDURE Init;
      PROCEDURE Hinzufuegen(VAR Satz: TSatz);
      PROCEDURE Zeigen;
      PROCEDURE Freigeben;
      FUNCTION LesenSatzbyM(M1_, M2_, M3_: INTEGER; VAR Satz: TSatz): BOOLEAN;
      FUNCTION LesenDatenbyM(M1_, M2_: Integer; VAR Bereich: TDaten): BOOLEAN;
   END;

PROCEDURE TSatz.Init(M1_, M2_, M3_ : INTEGER; PROG_, BEZEICHNER_, CODE_: STRING; N1_, N2_, L1_: INTEGER);
BEGIN
   M1 := M1_; M2 := M2_; M3 := M3_;
   N1 := N1_; N2 := N2_; L1 := L1_;
   PROG := PROG_; BEZEICHNER := BEZEICHNER_; CODE := CODE_;
   Folge := NIL;
END;

PROCEDURE TSatz.Zeigen;
BEGIN
   WriteLn('M1=', M1, ', M2=', M2, ', M3=', M3, ', PROG=', PROG,
           ', BEZ=', BEZEICHNER, ', CODE=', CODE, ', N1=', N1, ', N2=', N2, ', L1=', L1);
END;

PROCEDURE TDaten.Init;
BEGIN
   Kopf := NIL; Fuss := NIL; Anzahl := 0;
END;

PROCEDURE TDaten.Hinzufuegen(VAR Satz: TSatz);
VAR Neues: PSatz;
BEGIN
   New(Neues);
   Neues^ := Satz;
   Neues^.Folge := NIL;
   IF Kopf = NIL THEN
      Kopf := Neues
   ELSE
      Fuss^.Folge := Neues;
   Fuss := Neues;
   INC(Anzahl);
END;

PROCEDURE TDaten.Zeigen;
VAR Zeiger: PSatz;
BEGIN
   Zeiger := Kopf;
   WHILE Zeiger <> NIL DO BEGIN
      Zeiger^.Zeigen;
      Zeiger := Zeiger^.Folge;
   END;
END;

PROCEDURE TDaten.Freigeben;
VAR Zeiger, Temp: PSatz;
BEGIN
   Zeiger := Kopf;
   WHILE Zeiger <> NIL DO BEGIN
      Temp := Zeiger^.Folge;
      Dispose(Zeiger);
      Zeiger := Temp;
   END;
   Kopf := NIL; Fuss := NIL; Anzahl := 0;
END;

FUNCTION TDaten.LesenSatzbyM(M1_, M2_, M3_: INTEGER; VAR Satz: TSatz): BOOLEAN;
VAR Zeiger: PSatz;
BEGIN
   LesenSatzbyM := False;
   Zeiger := Kopf;
   WHILE Zeiger <> NIL DO BEGIN
      IF (Zeiger^.M1 = M1_) AND (Zeiger^.M2 = M2_) AND (Zeiger^.M3 = M3_) THEN BEGIN
         Satz := Zeiger^;
         LesenSatzbyM := True;
         Exit;
      END;
      Zeiger := Zeiger^.Folge;
   END;
END;

FUNCTION TDaten.LesenDatenbyM(M1_, M2_: INTEGER; VAR Bereich: TDaten): BOOLEAN;
VAR Zeiger: PSatz;
BEGIN
   Bereich.Freigeben;
   LesenDatenbyM := False;
   Zeiger := Kopf;
   WHILE Zeiger <> NIL DO BEGIN
      IF (Zeiger^.M1 = M1_) AND (Zeiger^.M2 = M2_) THEN BEGIN
         Bereich.Hinzufuegen(Zeiger^);
         LesenDatenbyM := True;
      END;
      Zeiger := Zeiger^.Folge;
   END;
END;

PROCEDURE LoadCSV(CONST FileName: STRING; VAR Daten: TDaten);
VAR
   CSVFile: TEXT;
   Zeile: STRING;
   M1_, M2_, M3_, N1_, N2_, L1_: INTEGER;
   PROG_, BEZEICHNER_, CODE_: STRING;
   NeuerSatz: TSatz;

   FUNCTION Str2Int(S: STRING): INTEGER;
   VAR w, Zahl: INTEGER;
   BEGIN
      Val(S, Zahl, w);
      IF w = 0 THEN Str2Int := Zahl
      ELSE Str2Int := -1;
   END;

   FUNCTION Strip: STRING;
   VAR k: INTEGER;
   BEGIN
      k := Pos(';', Zeile);
      IF k = 0 THEN k := LENGTH(Zeile) + 1;
      Strip := Copy(Zeile, 1, k - 1);
      Zeile := Copy(Zeile, k + 1, 255);
   END;

BEGIN
   Daten.Freigeben;
   Assign(CSVFile, FileName); Reset(CSVFile);
   ReadLn(CSVFile, Zeile);
   WHILE NOT EOF(CSVFile) DO BEGIN
      ReadLn(CSVFile, Zeile);
      M1_:=Str2Int(Strip); M2_:=Str2Int(Strip); M3_:=Str2Int(Strip); PROG_:=Strip;
      BEZEICHNER_:=Strip; CODE_:=Strip; N1_:=Str2Int(Strip); N2_:=Str2Int(Strip); L1_:=Str2Int(Strip);
      Neuersatz.Init(M1_, M2_, M3_, PROG_, BEZEICHNER_, CODE_, N1_, N2_, L1_);
      Daten.Hinzufuegen(NeuerSatz);
   END;
   Close(CSVFile);
END;

VAR
   Daten, Auswahl: TDaten;
   EinzelSatz: TSatz;

BEGIN
   Daten.Init; Auswahl.Init;
   LoadCSV('daten.csv', Daten);
   Daten.Zeigen;
   IF Daten.LesenSatzbyM(0, 0, 9, EinzelSatz) THEN
      WriteLn('Datensatz 0 0 9 : ', EinzelSatz.BEZEICHNER, ' ', EinzelSatz.CODE)
   ELSE
      WriteLn('Datensatz nicht gefunden.');
   IF Daten.LesenDatenbyM(0, 6, Auswahl) THEN BEGIN
      WriteLn('Gefundene Datensätze für M1=0, M2=6:');
      Auswahl.Zeigen;
   END ELSE
      WriteLn('Keine Datensätze für M1=0, M2=6 gefunden.');
   Daten.Freigeben; Auswahl.Freigeben;
   ReadLn;
END.
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Ich habe nun das Programm in allen drei Sprachen fertig - wenn ich sagen sollte wo es mir am besten gefällt, keine Ahnung. Python ist natürlich sehr kompakt:

Code: Alles auswählen

import csv
import logging

logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')

class MenueZeile:
    def __init__(self, m1, m2, m3, prog, bezeichner, code, n1, n2, l1):
        self.m1 = m1 ; self.m2 = m2 ; self.m3 = m3
        self.prog = prog ; self.bezeichner = bezeichner ; self.code = code
        self.n1 = n1 ; self.n2 = n2 ; self.l1 = l1

class MenueManager:
    def __init__(self, dateiname):
        self.daten = self.csv_lesen(dateiname)
        self.m1 = self.m2 = 0
        self.menue_stapel = []  
        self.bezeichnung = "Hauptmenü"
        self.menue_angezeigt = False

    def aktuelle_menuepunkte(self):
        return [zeile for zeile in self.daten if zeile.m1 == self.m1 and zeile.m2 == self.m2]

    def finde_menuepunkt(self, auswahl):
        return next((zeile for zeile in self.daten if zeile.m1 == self.m1 and zeile.m2 == self.m2 and zeile.m3 == auswahl), None)

    def zeige_menue(self):
        menuepunkte = self.aktuelle_menuepunkte()
        if not menuepunkte:
            logging.error("Menüzeilen nicht vorhanden.")
        else:
            print(f"\n{self.bezeichnung}:")
            for punkt in menuepunkte:
                print(f"{punkt.m3}: {punkt.bezeichner}")
                for i in range(punkt.l1):
                    print()
            print("\n0: Zurück")

    def hat_nachfolge_menue(self, n1, n2):
        return any(zeile for zeile in self.daten if zeile.m1 == n1 and zeile.m2 == n2)

    def bearbeite_auswahl(self, auswahl):
        if auswahl == 0:
            self.menue_angezeigt = False
            if self.menue_stapel:
                self.m1, self.m2, self.bezeichnung = self.menue_stapel.pop()
                return False
            else:
                logging.info("Programmende.")
                return True

        punkt = self.finde_menuepunkt(auswahl)
        if not punkt:
            logging.warning("Ungültige Auswahl. Keine Einträge für das gewählte Menü.")
            return False
            
        self.bezeichnung = punkt.bezeichner
        if punkt.prog.upper() == 'P':
            logging.info(f"Starte Programm: {punkt.code}")
  
        elif punkt.prog.upper() == 'M':
            if self.hat_nachfolge_menue(punkt.n1, punkt.n2):
                self.menue_stapel.append((self.m1, self.m2, self.bezeichnung))
                self.m1, self.m2 = punkt.n1, punkt.n2
                self.menue_angezeigt = False
            else:
                logging.warning("Ungültige Auswahl. Aufgerufenes Menü nicht gefunden.")
        else:
            logging.error("Falscher Programmcode.")
        
        return False

    def fuehre_menue_aus(self):
        ende = False
        while not ende:
            if not self.menue_angezeigt:
                self.zeige_menue()
                self.menue_angezeigt = True
            try:
                auswahl = int(input("Auswahl: "))
                ende = self.bearbeite_auswahl(auswahl)
            except ValueError:
                logging.error("Ungültige Eingabe. Bitte geben Sie eine Zahl ein.")

    def csv_lesen(self, dateiname):
        def str2int(string):
            try:
                return int(string)
            except ValueError:
                return -1
        daten = []
        with open(dateiname, newline='') as csvfile:
            for zeile in csv.DictReader(csvfile, delimiter=';'):
                if zeile['PROG'] != 'D':
                    daten.append(MenueZeile(
                        str2int(zeile['M1']), str2int(zeile['M2']), str2int(zeile['M3']),
                        zeile['PROG'], zeile['BEZEICHNER'], zeile['CODE'],
                        str2int(zeile['N1']), str2int(zeile['N2']), str2int(zeile['L1'])
                    ))
        return daten

def main():
    menue_manager = MenueManager('freiburg.csv')
    menue_manager.fuehre_menue_aus()

if __name__ == "__main__":
    main()
wobei mir die Spezialisten sicher wieder beweisen werden, daß es nicht über 100 Zeilen braucht sondern nur 20...

Mein Versuch in Java gefiel mir bis zum Sonntag eigentlich am besten, dann kam ich auf die Schnapsidee, ChatGPT mal zu fragen ob es Optimierungen oder stilistische Verbesserungen für Java 8 anbieten kann und heraus kam dann folgendes.

Code: Alles auswählen

package freiburg;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Scanner;
import java.util.Stack;
import java.util.stream.Collectors;

class Freiburg {

    enum ProgTyp { PROGRAMM("P"), MENUE("M"), DIVERSE("D");
        private String code;
        ProgTyp(String code) { this.code = code; }
        public String getCode() { return code; }

        public static ProgTyp fromCode(String code) {
            for (ProgTyp progTyp : values()) {
                if (progTyp.code.equalsIgnoreCase(code))
                   return progTyp;
            }
            throw new IllegalArgumentException("Ungültiger Programmtyp: " + code);
        }
    }

    static class TEintrag {
        int M1, M2, M3, N1, N2, L1;
        ProgTyp progTyp;
        String BEZEICHNER, CODE;

        TEintrag(int M1, int M2, int M3, ProgTyp progTyp, String BEZEICHNER, String CODE, int N1, int N2, int L1) {
          this.M1 = M1; this.M2 = M2; this.M3 = M3;
          this.progTyp = progTyp; this.BEZEICHNER = BEZEICHNER; this.CODE = CODE;
          this.N1 = N1; this.N2 = N2; this.L1 = L1;
        }
        void kopiereVon(TEintrag quelle) {
          this.M1 = quelle.M1; this.M2 = quelle.M2; this.M3 = quelle.M3;
          this.progTyp = quelle.progTyp; this.BEZEICHNER = quelle.BEZEICHNER; this.CODE = quelle.CODE;
          this.N1 = quelle.N1; this.N2 = quelle.N2; this.L1 = quelle.L1;
        }
    }

    static class TMenueManager {
        private static final String HAUPTMENUE_BEZEICHNUNG = "Hauptmenue";
        
        List<TEintrag> gesamt = new ArrayList<>();
        Stack<TEintrag> mStack = new Stack<>();
        int m1 = 0, m2 = 0;
        String bezeichnung = HAUPTMENUE_BEZEICHNUNG;
        boolean menueAngezeigt = false;

        Optional<TEintrag> findeMenuepunkt(int m1, int m2, int m3) {
            return gesamt.stream()
                    .filter(eintrag -> eintrag.M1 == m1 && eintrag.M2 == m2 && eintrag.M3 == m3)
                    .findFirst();
        }

        boolean hatNachfolgeMenue(int n1, int n2) {
            return gesamt.stream().anyMatch(eintrag -> eintrag.M1 == n1 && eintrag.M2 == n2);
        }

        List<TEintrag> aktuelleMenuepunkte(int m1, int m2) {
            return gesamt.stream()
                    .filter(eintrag -> eintrag.M1 == m1 && eintrag.M2 == m2)
                    .collect(Collectors.toList());
        }

        void zeigeMenue() {
            List<TEintrag> menuepunkte = aktuelleMenuepunkte(m1, m2);
            if (menuepunkte.isEmpty()) {
                System.out.println("Fehler: Menuezeilen nicht vorhanden.");
            } else {
                System.out.println("\n" + bezeichnung + ":");
                for (TEintrag punkt : menuepunkte) {
                    System.out.println(String.format("%d: %s", punkt.M3, punkt.BEZEICHNER));
                    for (int i = 0; i < punkt.L1; i++) System.out.println();
                }
                System.out.println("0: Zurueck");
            }
        }

        boolean bearbeiteAuswahl(int auswahl) {
            if (auswahl == 0) {
                menueAngezeigt = false;
                if (mStack.isEmpty()) {
                    System.out.println("Programmende.");
                    return true;
                }
                TEintrag temp = mStack.pop();
                m1 = temp.M1; m2 = temp.M2;
                bezeichnung = temp.BEZEICHNER;
                return false;
            }

            Optional<TEintrag> optionalPunkt = findeMenuepunkt(m1, m2, auswahl);
            if (!optionalPunkt.isPresent()) {
                System.out.println("Ungueltige Auswahl. Keine Eintraege fuer das gewaehlte Menue.");
                return false;
            }
            
            TEintrag punkt = optionalPunkt.get();
            bezeichnung = punkt.BEZEICHNER;
            if (punkt.progTyp.equals(ProgTyp.PROGRAMM)) {
                System.out.println("Starte Programm: " + punkt.CODE);
            } else if (punkt.progTyp.equals(ProgTyp.MENUE)) {
                if (hatNachfolgeMenue(punkt.N1, punkt.N2)) {
                    mStack.push(punkt);
                    m1 = punkt.N1; m2 = punkt.N2;
                    menueAngezeigt = false;
                } else {
                    System.out.println("Ungueltige Auswahl. Aufgerufenes Menue nicht gefunden.");
                }
            } else {
                System.out.println("Fehler: Falscher Programmcode.");
            }
            return false;
        }

        void fuehreMenueAus() {
            try (Scanner scanner = new Scanner(System.in)) {
                while (true) {
                    if (!menueAngezeigt) {
                        zeigeMenue();
                        menueAngezeigt = true;
                    }
                    System.out.print("Auswahl: ");
                    try {
                        int auswahl = Integer.parseInt(scanner.nextLine());
                        if (bearbeiteAuswahl(auswahl))
                            break;
                    } catch (NumberFormatException e) {
                        System.out.println("Ungueltige Eingabe. Bitte geben Sie eine Zahl ein.");
                    }
                }
            }
        }

        void loadCSV(String fileName) {
            try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
                br.lines().skip(1).forEach(line -> {
                    try {
                        String[] parts = line.split(";");
                        ProgTyp progTyp = ProgTyp.fromCode(parts[3]); 
                        TEintrag eintrag = new TEintrag(
                                Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]),
                                progTyp, parts[4], parts[5],
                                Integer.parseInt(parts[6]), Integer.parseInt(parts[7]), Integer.parseInt(parts[8]));
                        if (!progTyp.equals(ProgTyp.DIVERSE)) 
                            gesamt.add(eintrag);
                    } catch (Exception e) {
                        System.out.println("Fehler beim Einlesen der Zeile: " + line);
                    }
                });
            } catch (IOException e) {
                System.out.println("Fehler beim Lesen der Datei: " + e.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        TMenueManager menueManager = new TMenueManager();
        menueManager.loadCSV("daten.csv");
        menueManager.fuehreMenueAus();
        System.out.println("Beenden");
    }
}
Ich kann damit leben und habe wieder neue Sprachmittel gelernt, aber manches wie die enums oder das optional und selbst die Filter sind wohl eher gezwungen und naja ich werde das nicht so oft benutzen..

In Pascal habe ich erstmal Probleme lösen bzw Werkzeuge schaffen müssen. Natürlich gibt es ArrayLists und Stacks in Lazarus auch aber eben nicht in Turbo-Pascal und das war ja mein eigentliches Zielsystem. Also habe ich schrittweise die Java-Lösung konvertiert und bin am Ende zufrieden daß ich mit der bedingten Compilierung doch noch alles unter einen Hut bekam. Es ist nicht nur deswegen mit Abstand die längste Implementierung, ich bin halt oft sehr umständlich in Pascal obwohl (oder gerade?) weil ich die Sprache nun schon 40 Jahre benutze, Lazarus aber erst verhältnismäßig zaghaft.

Code: Alles auswählen

PROGRAM freiburg;

{$I OsWahl.inc}

USES Stdio, Stack, Liste;

TYPE
   PEintrag = ^TEintrag;
   TEintrag = RECORD
      M1, M2, M3, N1, N2, L1: INTEGER;
      PROG, BEZEICHNER, CODE: STRING[50];
   END;

   PMEintrag = {$IFDEF DOS} PListeElement {$ELSE} specialize TListe<TEintrag>.PListeElement {$ENDIF};
   TMListe   = {$IFDEF DOS} TListe {$ELSE} specialize TListe<TEintrag> {$ENDIF};

   TMenueManager = OBJECT
      Gesamt, Auswahl: TMListe;
      aktuell: TEintrag;
      MenueStapel: {$IFDEF DOS} TStack {$ELSE} specialize TStack<TEintrag> {$ENDIF};
      MenueAngezeigt: BOOLEAN;
      PROCEDURE Init;
      PROCEDURE Freigeben;
      PROCEDURE LoadCSV(CONST FileName: STRING);
      PROCEDURE Zeige_Menue;
      PROCEDURE Inhalt(Zeiger: PMEintrag; VAR Eintrag: TEintrag);
      FUNCTION  Bearbeite_Auswahl(wahl: CHAR): BOOLEAN;
      FUNCTION  Finde_Menuepunkt(M1, M2, M3: INTEGER; VAR Ergebnis: TEintrag): BOOLEAN;
      FUNCTION  Hat_Nachfolge_Menue(N1, N2: INTEGER): BOOLEAN;
      FUNCTION  Aktuelle_Menuepunkte(VAR Ergebnis: TMListe): BOOLEAN;
      PROCEDURE Fuehre_Menue_Aus;
   END;

PROCEDURE Init_Eintrag(VAR eintrag: TEintrag);
BEGIN
   WITH Eintrag DO BEGIN
      M1 := 0; M2 := 0; M3 := 0; N1 := 0; N2 := 0; L1 := 0;
      PROG := ''; CODE := ''; BEZEICHNER := '';
   END;
END;

PROCEDURE TMenueManager.Init;
BEGIN
   Gesamt.Init {$IFDEF DOS} (SizeOf(TEintrag)) {$ENDIF};;
   Auswahl.Init {$IFDEF DOS} (SizeOf(TEintrag)) {$ENDIF};;
   Init_Eintrag(aktuell); aktuell.BEZEICHNER := 'Hauptmenue';
   MenueAngezeigt := False;
   MenueStapel.Init {$IFDEF DOS} (SizeOf(TEintrag)) {$ENDIF};
END;

PROCEDURE TMenueManager.Freigeben;
BEGIN
   Gesamt.Clear; Auswahl.Clear;
   MenueStapel.Clear;
END;

PROCEDURE TMenueManager.Inhalt(Zeiger: PMEintrag; VAR Eintrag: TEintrag);
{$IFDEF DOS} VAR Temp: PEintrag; {$ENDIF}
BEGIN
   {$IFDEF DOS} Temp := Zeiger^.Data; Eintrag := Temp^; {$ELSE} Eintrag := Zeiger^.Data; {$ENDIF}
END;

FUNCTION TMenueManager.Finde_Menuepunkt(M1, M2, M3: INTEGER; VAR Ergebnis: TEintrag): BOOLEAN;
VAR Zeiger: PMEintrag;
    Eintrag: TEintrag;
    wert: BOOLEAN;
BEGIN
   wert := False;
   Zeiger := Gesamt.Head;
   WHILE (Zeiger <> NIL) AND NOT wert DO BEGIN
      Inhalt(Zeiger, Eintrag);     
      IF (Eintrag.M1 = M1) AND (Eintrag.M2 = M2) AND (Eintrag.M3 = M3) THEN BEGIN
         Ergebnis := Eintrag; wert := True;
      END;
      Zeiger := Zeiger^.Next;
   END;
   Finde_Menuepunkt := wert;
END;

FUNCTION TMenueManager.Hat_Nachfolge_Menue(N1, N2: INTEGER): BOOLEAN;
VAR Zeiger: PMEintrag;
    Eintrag: TEintrag;
    wert: BOOLEAN;
BEGIN
   wert := False;
   Zeiger := Gesamt.Head;
   WHILE (Zeiger <> NIL) AND NOT wert DO BEGIN
      Inhalt(Zeiger, Eintrag);     
      IF (Eintrag.M1 = N1) AND (Eintrag.M2 = N2)
         THEN wert := True;
      Zeiger := Zeiger^.Next;
   END;
   Hat_Nachfolge_Menue := wert;
END;

FUNCTION TMenueManager.Aktuelle_Menuepunkte(VAR Ergebnis: TMListe): BOOLEAN;
VAR Zeiger: PMEintrag;
    Eintrag: TEintrag;
BEGIN
   Ergebnis.Clear;
   Zeiger := Gesamt.Head;
   WHILE Zeiger <> NIL DO BEGIN
      Inhalt(Zeiger, Eintrag);     
      IF (Eintrag.M1 = aktuell.M1) AND (Eintrag.M2 = aktuell.M2)
         THEN Ergebnis.Add({$IFDEF DOS}@{$ENDIF}Eintrag);
      Zeiger := Zeiger^.Next;
   END;
   Aktuelle_Menuepunkte := Ergebnis.Anzahl <> 0;
END;

PROCEDURE TMenueManager.Zeige_Menue;
VAR Zeiger: PMEintrag;
    EIntrag: TEintrag;
    i: INTEGER;
BEGIN
   IF NOT Aktuelle_Menuepunkte(Auswahl) THEN
      Writeln('Fehler: Menuezeilen nicht vorhanden.')
   ELSE BEGIN
      Writeln(aktuell.BEZEICHNER, ':');
      Zeiger := Auswahl.Head;
      WHILE Zeiger <> NIL DO BEGIN
         Inhalt(Zeiger, Eintrag);     
         Writeln(Eintrag.M3, ': ', Eintrag.BEZEICHNER);
         FOR i := 1 TO Eintrag.L1 DO Writeln;
         Zeiger := Zeiger^.Next;
      END;
      Writeln('0: Zurueck');
   END;
END;

FUNCTION TMenueManager.Bearbeite_Auswahl(wahl: CHAR): BOOLEAN;
VAR Punkt: TEintrag;
    ziffer,w: INTEGER;
BEGIN
   Bearbeite_Auswahl := False;
   IF wahl in ['0'..'9'] THEN VAL(wahl, ziffer, w)
   ELSE BEGIN
      Writeln('Ungueltige Auswahl.');
      Exit;
   END;
   IF ziffer = 0 THEN BEGIN
      MenueAngezeigt := False;
      IF MenueStapel.IsEmpty THEN BEGIN
         Writeln('Programmende.');
         Bearbeite_Auswahl := True; Exit;
      END;
      MenueStapel.Pop({$IFDEF DOS}@{$ENDIF}aktuell);
      Exit;
   END;
   IF NOT Finde_Menuepunkt(aktuell.M1, aktuell.M2, ziffer, Punkt) THEN BEGIN
      Writeln('Ungueltige Auswahl.');
      Exit;
   END;
   aktuell.BEZEICHNER := Punkt.BEZEICHNER;
   CASE UpCase(Punkt.PROG[1]) OF
     'P': Writeln('Starte Programm: ', Punkt.CODE);
     'M': IF Hat_Nachfolge_Menue(Punkt.N1, Punkt.N2) THEN BEGIN
             MenueStapel.Push({$IFDEF DOS}@{$ENDIF}aktuell);
             aktuell.M1 := Punkt.N1; aktuell.M2 := Punkt.N2;
             MenueAngezeigt := False;
          END
          ELSE Writeln('Ungueltige Auswahl. Aufgerufenes Menue nicht gefunden.');
      ELSE Writeln('Fehler: Falscher Programmcode.');
   END;
END;

PROCEDURE TMenueManager.Fuehre_Menue_Aus;
VAR wahl: CHAR;
    ende: BOOLEAN;
BEGIN
   ende := False;
   REPEAT
      IF NOT MenueAngezeigt THEN BEGIN
         Zeige_Menue; MenueAngezeigt := True;
      END;
      Write('Auswahl: '); ReadLn(wahl);
      ende := Bearbeite_Auswahl(wahl);
   UNTIL ende;
END;

PROCEDURE TMenueManager.LoadCSV(CONST FileName: STRING);
VAR
   CSVFile: TEXT;
   Eintrag: TEintrag;
   Zeile: STRING;
   FUNCTION Strip: STRING;
   VAR k: INTEGER;
   BEGIN
      k := Pos(';', Zeile); IF k = 0 THEN k := LENGTH(Zeile) + 1;
      Strip := Copy(Zeile, 1, k - 1);
      Zeile := Copy(Zeile, k + 1, 255);
   END;
BEGIN
   Assign(CSVFile, FileName); Reset(CSVFile);
   ReadLn(CSVFile, Zeile);
   WHILE NOT EOF(CSVFile) DO BEGIN
      ReadLn(CSVFile, Zeile);
      WITH Eintrag DO BEGIN
         M1 := Str2Int(Strip); M2 := Str2Int(Strip); M3 := Str2Int(Strip);
         PROG := Strip; BEZEICHNER := Strip; CODE := Strip;
         N1 := Str2Int(Strip); N2 := Str2Int(Strip); L1 := Str2Int(Strip);
      END;
      IF Eintrag.PROG <> 'D' THEN
         Gesamt.Add({$IFDEF DOS}@{$ENDIF}Eintrag);
   END;
   Close(CSVFile);
END;

VAR MenueManager: TMenueManager;

BEGIN
   WITH Menuemanager DO BEGIN
      Init; LoadCSV('daten.csv');
      Fuehre_Menue_Aus;
      Freigeben;
   END;
   Writeln('Beenden'); Readln;
END.






UNIT Liste;

{$I OsWahl.inc}

{$IFNDEF DOS} {$MODE OBJFPC} {$H+} {$ENDIF}

INTERFACE

TYPE

  {$IFDEF DOS}

  PListeElement = ^TListeElement;
  TListeElement = RECORD
    Data: Pointer;
    Next: PListeElement;
  END;

  TListe = OBJECT
    Public
      Head, Tail: PListeElement;
      DataSize: Integer;
      Count: Integer;

  {$ELSE}

  GENERIC TListe<T> = OBJECT
    Private
      TYPE
        PListeElement = ^TListeElement;
        TListeElement = RECORD
          Data: T;
          Next: PListeElement;
        END;
      VAR
        Head, Tail: PListeElement;
        Count: Integer;

  {$ENDIF}

  Public
    PROCEDURE Init {$IFDEF DOS} (SizeOfData: Integer) {$ENDIF} ;
    PROCEDURE Add(Data: {$IFDEF DOS} Pointer {$ELSE} T {$ENDIF});
    FUNCTION Get(Index: Integer; {$IFDEF DOS} Data: Pointer {$ELSE} VAR Data: T {$ENDIF}): BOOLEAN;
    FUNCTION Remove(Index: Integer): BOOLEAN;
    FUNCTION IsEmpty: BOOLEAN;
    PROCEDURE Clear;
    FUNCTION Anzahl: Integer;
    FUNCTION Start: PListeElement;
  END;

IMPLEMENTATION

{$IFDEF DOS} USES DOS; {$ENDIF}

PROCEDURE TListe.Init {$IFDEF DOS} (SizeOfData: Integer) {$ENDIF} ;
BEGIN
  Head := NIL;
  Tail := NIL;
  Count := 0;
  {$IFDEF DOS} DataSize := SizeOfData; {$ENDIF}
END;

PROCEDURE TListe.Add(Data: {$IFDEF DOS} Pointer {$ELSE} T {$ENDIF});
VAR
  NewElement: PListeElement;
BEGIN
  New(NewElement);
  {$IFDEF DOS}
    GetMem(NewElement^.Data, DataSize);
    Move(Data^, NewElement^.Data^, DataSize);
  {$ELSE}
    NewElement^.Data := Data;
  {$ENDIF}
  NewElement^.Next := NIL;

  IF Tail = NIL THEN
    Head := NewElement
  ELSE
    Tail^.Next := NewElement;

  Tail := NewElement;
  Inc(Count);
END;

FUNCTION TListe.Get(Index: Integer; {$IFDEF DOS} Data: Pointer {$ELSE} VAR Data: T {$ENDIF}): BOOLEAN;
VAR
  Current: PListeElement;
  CountIndex: Integer;
BEGIN
  Current := Head;
  CountIndex := 0;
  WHILE (Current <> NIL) AND (CountIndex < Index) DO BEGIN
    Current := Current^.Next;
    Inc(CountIndex);
  END;

  IF Current = NIL THEN
    Get := FALSE
  ELSE BEGIN
    {$IFDEF DOS}
      Move(Current^.Data^, Data^, DataSize);
    {$ELSE}
      Data := Current^.Data;
    {$ENDIF}
    Get := TRUE;
  END;
END;

FUNCTION TListe.Remove(Index: Integer): BOOLEAN;
VAR
  Current, Prev: PListeElement;
  CountIndex: Integer;
BEGIN
  Remove := FALSE;
  IF Head = NIL THEN EXIT;

  Current := Head;
  Prev := NIL;
  CountIndex := 0;

  WHILE (Current <> NIL) AND (CountIndex < Index) DO BEGIN
    Prev := Current;
    Current := Current^.Next;
    Inc(CountIndex);
  END;

  IF Current = NIL THEN EXIT;

  IF Prev = NIL THEN
    Head := Current^.Next
  ELSE
    Prev^.Next := Current^.Next;

  IF Current = Tail THEN
    Tail := Prev;

  {$IFDEF DOS} FreeMem(Current^.Data, DataSize); {$ENDIF}
  Dispose(Current);
  Dec(Count);
  Remove := TRUE;
END;

FUNCTION TListe.IsEmpty: BOOLEAN;
BEGIN
  IsEmpty := Head = NIL;
END;

PROCEDURE TListe.Clear;
VAR
  TempElement: PListeElement;
BEGIN
  WHILE Head <> NIL DO BEGIN
    TempElement := Head;
    Head := Head^.Next;
    {$IFDEF DOS} FreeMem(TempElement^.Data, DataSize); {$ENDIF}
    Dispose(TempElement);
  END;
  Tail := NIL;
  Count := 0;
END;

FUNCTION TListe.Anzahl: Integer;
BEGIN
  Anzahl := Count;
END;

FUNCTION TListe.Start: PListeElement;
BEGIN
  Start := Head;
END;

BEGIN
END.

Antworten