Hier ist eine neue Version des Konstruktors. Er gibt eine Fehlermeldung aus, wenn ein Bruch konstruiert wird, dessen Nenner Null ist.
def __init__(self, zaehler, nenner):
self._zaehler = zaehler
self._nenner = nenner
if nenner == 0:
print("Der Nenner ist Null!")
return
gcd = self._ggT(zaehler, nenner)
self._zaehler = zaehler // gcd
self._nenner = nenner // gcd
Der folgende Aufruf demonstriert den Effekt dieser Änderung.
>>> Bruch(7,0)
Der Nenner ist Null!
7/0
Hier sind Definitionen der verbleibenden Grundrechenarten inklusive dazu verwendeter Hilfsmethoden.
def negativ(self):
return Bruch(-self._zaehler, self._nenner)
def minus(self, other):
return self.plus(other.negativ())
def kehrwert(self):
return Bruch(self._nenner, self._zaehler)
def durch(self, other):
return self.mal(other.kehrwert())
Zur Definition der Subtraktion definieren wir eine Negationsmethode negativ
. Zur Definition der Division definieren wir eine Methode kehrwert
. Beide Methoden sind unabhängig von unserer Verwendung nützlich, weshalb wir ihren Namen keinen Unterstrich voranstellen.
Da Brüche gekürzt dargestellt werden, können wir die Vergleichsmethode wie folgt definieren.
def ist_gleich(self, other):
return self._zaehler == other._zaehler and self._nenner == other._nenner
Als Namen für die Klasse komplexer Zahlen wählen wir Komplex
.
class Komplex
Wir definieren einen Konstruktor, der Real- und Imaginärteil als Parameter erwartet und als entsprechende (private) Attribute speichert.
def __init__(self, real, imag):
self._real = real
self._imag = imag
Für diese Attribute definieren wir lesende Zugriffsmethoden.
def real(self):
return self._real
def imag(self):
return self._imag
Als Zeichenketten-Darstellung für komplexe Zahlen wählen wir die Form a+bi
mit Vereinfachungen für einige Sonderfälle. Zum Beispiel Stellen wir die Zahl 1+0i
als 1
dar, die Zahl 0-i
als -i
und so weiter.
def __repr__(self):
return str(self)
def __str__(self):
if self._imag == 0:
return str(self._real)
if self._real == 0:
return self._imag_str()
if self._imag < 0:
return str(self._real) + self._imag_str()
return str(self._real) + "+" + self._imag_str()
Die Hilfsmethode _imag_str
gibt die Zeichenkettendarstellung des Imaginärteils zurück und soll nicht von außen verwendet werden.
def _imag_str(self):
if self._imag == 1:
return "i"
if self._imag == -1:
return "-i"
return str(self._imag) + "i"
Wir können nun die Zahl i
wie folgt erzeugen und anzeigen lassen.
>>> Komplex.new(0,1)
i
Zur Addition zweier komplexer Zahlen addieren wir deren Real- und Imaginärteile getrennt voneinander. Ist das Argument keine komplexe Zahl interpretieren wir es als reele Zahl und erzeugen eine entsprechende komplexe Zahl vor der Addition.
def plus(self, other):
if type(other) == Komplex:
return Komplex(
self._real + other._real,
self._imag + other._imag
)
else:
return self.plus(Komplex(other, 0))
Durch diesen Trick ist es möglich, komplexe Zahlen mit reellen zu addieren - zumindest, wenn das erste Argument eine komplexe Zahl ist:
>>> i = Komplex.new(0,1)
>>> i.plus(1)
1+i
Zur Subtraktion komplexer Zahlen definieren wir zunächst die Negation und verwenden dann die Addition zum Subtrahieren.
def negativ(self):
return Komplex(-self._real, -self._imag)
def minus(self, other):
if type(other) == Komplex:
return self.plus(other.negativ())
else:
return self.plus(Komplex(-other, 0))
Multiplikation und Division implementieren wir auf Basis von Absolutbetrag und Winkel im Bogenmaß (Radiant), die mit Hilfe vordefinierter mathematischer Funktionen aus Real- und Imaginärteil berechnet werden können, die wir mit import math
importieren.
def abs(self):
return math.sqrt(self._real ** 2 + self._imag ** 2)
def rad(self):
return math.atan2(self._imag, self._real)
Zur Multiplikation unterscheiden wir wieder ob das Argument eine komplexe Zahl ist. Falls nicht, interpretieren wir das Argument als reele Zahl und multiplizieren Real- und Imaginärteil getrennt voneinander mit dieser. Ansonsten berechnen wir das Ergebnis in Polarkoordinaten und erzeugen aus diesen das Ergebnis.
def mal(self, other):
if type(other) == Komplex:
return polar(self.abs() * other.abs(), self.rad() + other.rad())
else:
return Komplex(self._real * other, self._imag * other)
Zur Division verfahren wir analog und die Definition der Klasse Komplex
ist beendet.
def durch(self, other):
if type(other) == Komplex:
return polar(self.abs() / other.abs(), self.rad() - other.rad())
else:
return Komplex(self._real / other, self._imag / other)
Die hier verwendete Funktion polar
zur Konstruktion einer komplexen Zahl aus Polarkoordinaten definieren wir (außerhalb der Klassendefinition) wie folgt.
def polar(abs, rad):
return Komplex(math.cos(rad), math.sin(rad)).mal(abs)
Hierbei wird wieder die Multiplikation auf komplexen Zahlen verwendet, um mit dem Absolutbetrag zu multiplizieren. Unsere Multiplikations-Methode und die polar
-Funktion rufen sich also gegenseitig auf, allerdings nicht endlos, da der Absolutbetrag eine reelle Zahl ist.
Hier sind einige Beispielaufrufe zum Testen der Implementierung.
>>> i = Komplex(0,1)
>>> i.mal(i)
-1.0+1.2246467991473532e-16i
>>> i.durch(i)
1.0
>>> i.plus(1).mal(i.minus(1)).durch(2)
-1.0000000000000002+1.2246467991473535e-16i
>>> i.plus(2).mal(i.plus(3))
5.000000000000001+5.0i
>>> i.plus(2).durch(i.plus(3))
0.7+0.09999999999999999i
Die Methode zum Einzahlen erweitern wir wie folgt, um die Einzahlung negativer Beträge zu verhindern.
def einzahlen(self, betrag):
if 0 <= betrag:
self._guthaben = self._guthaben + betrag
return True
else:
return False
Wir geben statt self
nun einen Wahrheitswert zurück, an dem aufrufender Programmcode erkennen kann, ob die Einzahlung erfolgreich war.
Beim Abheben testen wir zusätzlich, ob der auszuzahlende Betrag vom Guthaben gedeckt ist und geben wieder einen Wahrheitswert zurück.
def abheben(self, betrag):
if 0 <= betrag and betrag <= self._guthaben:
self._guthaben = self._guthaben - betrag
return True
else:
return False
Da Ein- und Auszahlungen jetzt fehlschlagen können, müssen wir die Überweisungs-Methode so anpassen, dass sie eine Einzahlung nur genau dann vornimmt, wenn auch die Auszahlung erfolgreich war. Dazu testen wir, ob die Auszahlung erfolgreich war, bevor wir die Einzahlung veranlassen.
def ueberweisen(self, other, betrag):
if self.abheben(betrag):
return other.einzahlen(betrag)
else:
return False
Die Einzahlung ist hier immer erfolgreich, da sie nur bei negativem Betrag fehl schlägt. In diesem Fall wäre aber schon die Auszahlung fehlgeschlagen und die Einzahlung garnicht veranlasst worden.
Um Verzinsung zu implementieren fügen wir dem Konstruktor ein Argument für den Zinssatz hinzu, der in einem Attribut gespeichert wird.
def __init__(self, zinssatz):
self._guthaben = 0.0
self._zinssatz = zinssatz
Auf dieses Attribut können wir nun in der Methode zum Verzinsen zugreifen.
def verzinsen(self):
self._guthaben = self._guthaben * (1 + self._zinssatz)
return self
class Stack:
def __init__(self):
self._elems = []
def is_empty(self):
return len(self._elems) == 0
def top(self):
return self._elems[len(self._elems)-1]
def push(self, elem):
self._elems.append(elem)
return self
def pop(self):
return self._elems.pop()
def __repr__(self):
return str(self)
def __str__(self):
result = "Stack:"
for i in range(0, len(self._elems)):
result = result + " " + str(self._elems[i])
return result
class Queue:
def __init__(self):
self._elems = []
def is_empty(self):
return len(self._elems) == 0
def first(self):
return self._elems[0]
def enqueue(self, elem):
self._elems.append(elem)
return self
def dequeue(self):
self._elems.pop(0)
return self
def __repr__(self):
return str(self)
def __str__(self):
result = "Queue:"
for i in range(0, len(self._elems)):
result = result + " " + str(self._elems[i])
return result