4.2. Übungen zu Stapeln#

4.2.1. Höhe des Stapels bestimmen#

In der folgenden Zelle siehst du die Klasse Stapel, die wir bereits entwickelt haben. Schau dir nun insb. die Methode anzahl_elemente an, mit der man die “Höhe” des Stapels bestimmen kann.

Es geht natürlich besser - weißt du wie?

Aufgabe: Ändere die Klasse Stapel und insb. die Methode anzahl_elemente, so dass die Höhe des Stapels nun geschickt verwaltet und ohne Aufwand zurückgegeben wird.

class Stapel:

    class Knoten:
        def __init__(self, inhalt, naechster=None):  # Wenn für naechster kein Wert angegeben wird, ist er None (Standardwert)
            self.inhalt = inhalt
            self.naechster = naechster

    def __init__(self):
        self.anfang: Stapel.Knoten|None = None

    def push(self, daten):
        """legt ein neues Element auf den Stapel"""
        neuer_knoten = Stapel.Knoten(daten, self.anfang)  # Der neue Knoten zeigt auf den bisherigen Anfang
        self.anfang = neuer_knoten  # Der neue Knoten ist jetzt der Anfang

    def pop(self):
        """entfernt das oberste Element vom Stapel und gibt es zurück"""
        if self.anfang is None:
            raise IndexError("Pop-Operation auf leerem Stapel nicht möglich")
        inhalt = self.anfang.inhalt  # Inhalt des ersten Knotens merken
        self.anfang = self.anfang.naechster  # Anfang auf den nächsten Knoten setzen
        return inhalt

    def top(self):
        """gibt das oberste Element des Stapels zurück, ohne es zu entfernen"""
        if self.anfang is None:
            raise IndexError("Top-Operation auf leerem Stapel nicht möglich")
        return self.anfang.inhalt

    def ist_leer(self):
        """gibt True zurück, wenn der Stapel leer ist, sonst False"""
        return self.anfang is None  # Kurzform für if self.anfang is None: return True else: return False
    
    def anzahl_elemente(self):
        """gibt die Anzahl der Elemente auf dem Stapel zurück"""
        # ACHTUNG: Diese Methode ist ineffizient, da sie die gesamte interne Liste durchlaufen muss!
        anzahl = 0
        knoten = self.anfang
        while knoten is not None:
            anzahl += 1
            knoten = knoten.naechster
        return anzahl
    
# Test
s = Stapel()
for i in range(100000):
    s.push(i)
print(s.anzahl_elemente())  # Bei der schlechten Implementierung dauert das lange
100000

Lösung:

Hide code cell content
class Stapel:

    class Knoten:
        def __init__(self, inhalt, naechster=None):  # Wenn für naechster kein Wert angegeben wird, ist er None (Standardwert)
            self.inhalt = inhalt
            self.naechster = naechster

    def __init__(self):
        self.anfang: Stapel.Knoten|None = None
        self.hoehe = 0   # TEIL DER LÖSUNG

    def push(self, daten):
        """legt ein neues Element auf den Stapel"""
        neuer_knoten = Stapel.Knoten(daten, self.anfang)  # Der neue Knoten zeigt auf den bisherigen Anfang
        self.anfang = neuer_knoten  # Der neue Knoten ist jetzt der Anfang
        self.hoehe += 1   # TEIL DER LÖSUNG

    def pop(self):
        """entfernt das oberste Element vom Stapel und gibt es zurück"""
        if self.anfang is None:
            raise IndexError("Pop-Operation auf leerem Stapel nicht möglich")
        inhalt = self.anfang.inhalt  # Inhalt des ersten Knotens merken
        self.anfang = self.anfang.naechster  # Anfang auf den nächsten Knoten setzen
        self.hoehe -= 1   # TEIL DER LÖSUNG
        return inhalt

    def top(self):
        """gibt das oberste Element des Stapels zurück, ohne es zu entfernen"""
        if self.anfang is None:
            raise IndexError("Top-Operation auf leerem Stapel nicht möglich")
        return self.anfang.inhalt

    def ist_leer(self):
        """gibt True zurück, wenn der Stapel leer ist, sonst False"""
        return self.anfang is None  # Kurzform für if self.anfang is None: return True else: return False
    
    def anzahl_elemente(self):
        """gibt die Anzahl der Elemente auf dem Stapel zurück"""
        return self.hoehe   # TEIL DER LÖSUNG

# Test
# s = Stapel()
# s.push(1)
# print(s.anzahl_elemente())  # Ausgabe: 1
# s.push(2)
# s.push(3)
# print(s.anzahl_elemente())  # Ausgabe: 3
# s.pop()
# print(s.anzahl_elemente())  # Ausgabe: 2
# s.pop()
# s.pop()
# print(s.anzahl_elemente())  # Ausgabe: 0

4.2.2. Aufgabe: Umgekehrte polnische Notation (UPN)#

In der umgekehrten polnischen Notation (UPN) für Rechenausdrücke werden die Operanden vor den Operatoren geschrieben.

Beispiel: 3 4 + 5 * bedeutet (3 + 4) * 5 = 35

Aufgabe: Schreibe eine Funktion, die einen String in UPN auswertet.

Hinweis

Umgangsprachlich kann man die Berechnung so beschreiben:

  • Man durchläuft nacheinander alle Elemente des Ausdrucks (jeweils durch Leerzeichen getrennt)

  • Trifft man auf eine Zahl, legt man sie auf den Stack

  • Trifft man auf einen Operator op

    • nimmt man die beiden obersten Elemente vom Stack

    • und verrechnet sie mit dem Operator op.

    • Das Ergebnis legt man wieder auf den Stack

  • Zum Schluss gibt man das oberste Element des Stacks zurück.

Hinweise:

  • Zerlege den Eingabe-String zuerst mit der split-Methode in Einzelelemente. (Hier findest du eine Erklärung zu split.)

  • Dann bearbeite mit einer Schleife jedes Element.

  • Nutze die unten definierte Funktion ist_zahl, um zwischen Zahlen und Operatoren zu unterscheiden.

  • Ach ja, und falls das noch nicht offensichtlich war: Nutze einen Stapel!

def ist_zahl(string: str):
    """prüft, ob ein String eine Zahl ist. Bsp. ist_zahl("-123") -> True, ist_zahl("abc") -> False"""
    try:
        int(string)
        return True
    except ValueError:
        return False
def berechne_upn(eingabe_im_upn_format: str) -> int:
    """berechnet den Wert eines UPN-Ausdrucks und gibt ihn zurück.
    Beispiel: berechne_upn("3 4 + 6 *") liefert 42"""

    ... # Hier kommt dein Code hin
    
    return None  # Hier musst du den berechneten Wert des Term zurückgeben


# Test: Liste mit Beispielen (jeweils UPN-Ausdruck und erwartetes Ergebnis)
print("Tests für die Funktion berechne_upn:")
beispiele = [
    ("3 4 +", 7),
    ("3 4 -", -1),
    ("3 4 *", 12),
    ("8 4 //", 2),
    ("3 4 + 6 *", 42),
    ("6 7 * 0 *", 0),
    ("2 3 + 4 * 14 + 2 // 20 - 0 3 - *", 9),
    ("2 3 + 4 * 14 + 2 // 20 - -3 *", 9),
    ("42 1 1 - //", "Fehler: Division durch 0"),
]
for upn, erwartet in beispiele:
    try:
        ergebnis = berechne_upn(upn)
        if ergebnis == erwartet:
            print(f"Richtig: {upn} ergibt {ergebnis}")
        else:
            print(f"Falsch: {upn} ergibt {ergebnis}, erwartet war {erwartet}")
    except Exception as e:
        if str(e) == erwartet:
            print(f"Richtig: {upn} wirft {e}")
        else:
            print(f"Falsch: {upn} wirft {e}, erwartet war {erwartet}")
Tests für die Funktion berechne_upn:
Falsch: 3 4 + ergibt None, erwartet war 7
Falsch: 3 4 - ergibt None, erwartet war -1
Falsch: 3 4 * ergibt None, erwartet war 12
Falsch: 8 4 // ergibt None, erwartet war 2
Falsch: 3 4 + 6 * ergibt None, erwartet war 42
Falsch: 6 7 * 0 * ergibt None, erwartet war 0
Falsch: 2 3 + 4 * 14 + 2 // 20 - 0 3 - * ergibt None, erwartet war 9
Falsch: 2 3 + 4 * 14 + 2 // 20 - -3 * ergibt None, erwartet war 9
Falsch: 42 1 1 - // ergibt None, erwartet war Fehler: Division durch 0

Lösung:

Hide code cell content
def berechne_upn(eingabe_im_upn_format: str) -> int:
    stapel = Stapel()
    for token in eingabe_im_upn_format.split():
        if ist_zahl(token):  # Wenn das Token eine (positive) Zahl ist
            stapel.push(int(token))
        else:
            b = stapel.pop()
            a = stapel.pop()
            if token == "+":
                stapel.push(a + b)
            elif token == "-":
                stapel.push(a - b)
            elif token == "*":
                stapel.push(a * b)
            elif token == "//":
                if b == 0:
                    raise ValueError("Fehler: Division durch 0")
                stapel.push(a // b)
            else:
                raise ValueError(f"Unbekanntes Token {token}")
    return stapel.pop()


# Test: Liste mit Beispielen (jeweils UPN-Ausdruck und erwartetes Ergebnis)
print("Tests für die Funktion berechne_upn:")
beispiele = [
    ("3 4 +", 7),
    ("3 4 -", -1),
    ("3 4 *", 12),
    ("8 4 //", 2),
    ("3 4 + 6 *", 42),
    ("6 7 * 0 *", 0),
    ("2 3 + 4 * 14 + 2 // 20 - 0 3 - *", 9),
    ("2 3 + 4 * 14 + 2 // 20 - -3 *", 9),
    ("42 1 1 - //", "Fehler: Division durch 0"),
]
for upn, erwartet in beispiele:
    try:
        ergebnis = berechne_upn(upn)
        if ergebnis == erwartet:
            print(f"Richtig: {upn} ergibt {ergebnis}")
        else:
            print(f"Falsch: {upn} ergibt {ergebnis}, erwartet war {erwartet}")
    except Exception as e:
        if str(e) == erwartet:
            print(f"Richtig: {upn} wirft {e}")
        else:
            print(f"Falsch: {upn} wirft {e}, erwartet war {erwartet}")
Richtig: 3 4 + ergibt 7
Richtig: 3 4 - ergibt -1
Richtig: 3 4 * ergibt 12
Richtig: 8 4 // ergibt 2
Richtig: 3 4 + 6 * ergibt 42
Richtig: 6 7 * 0 * ergibt 0
Richtig: 2 3 + 4 * 14 + 2 // 20 - 0 3 - * ergibt 9
Richtig: 2 3 + 4 * 14 + 2 // 20 - -3 * ergibt 9
Richtig: 42 1 1 - // wirft Fehler: Division durch 0