Wyzwanie Python #4: Rozwiązanie
Poniżej rozwiązanie do naszego czwartego wyzwania
Funkcja kwadratowa
Zaczynamy od konstruktora, który definiuje trzy pola: a
, b
, c
. Następnie wprowadzamy dwie metody pomocnicze, które, choć nie wpłyną na rozwiązanie równania kwadratowego, wydają się być bardzo naturalnymi składowymi takiej klasy: metoda wypisz()
, która po prostu wypisuje funkcję kwadratową na ekran, oraz oblicz_wartosc()
, która oblicza wartość funkcji kwadratowej w zadanym punkcie.
Przejdźmy teraz do metody rozwiaz()
. Tutaj przede wszystkim musimy dobrze zidentyfikować wszelkie przypadki szczególne oraz ich obsługę. Przyjęliśmy następującą konwencję: funkcja zawsze zwraca dwuelementową krotkę. Gdy jest zero rozwiązań, krotka ma wartości nan
, gdy jest ich nieskończenie wiele, wartościami są nieskończoności (inf
). Gdy jest jedno rozwiązanie, powtarzamy je dwa razy. Wymieńmy przypadki szczególne:
- Gdy funkcja jest funkcją stałą, czyli $a=b=0$. Wtedy mamy nieskończenie wiele rozwiązań, gdy $c=0$ oraz ani jednego, gdy $c\neq 0$.
- Funkcja jest liniowa, gdy $a=0$. Wtedy rozwiązanie jest jedno: $x_0 = x_1 = -\frac{c}{b}$.
- Funkcja jest kwadratowa, gdy $a\neq 0$. Wtedy jednak nadal mamy parę podprzypadków: gdy $\Delta$ jest mniejsza od zera, nie mamy żadnego rozwiązania. Gdy jest większa lub równa zero, stosujemy odpowiednie wzory na oba rozwiązania (tutaj wpada także przypadek, gdy $\Delta = 0$ i mamy tak naprawdę jedno rozwiązanie).
Aby obliczyć pierwiastek kwadratowy, importujemy pakiet math
.
Na końcu main()
, w którym testujemy poszczególne przypadki.
import math
class FunkcjaKwadratowa:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def wypisz(self):
print(f"{self.a}*x^2+{self.b}*x+{self.c}")
def oblicz_wartosc(self, x):
return self.a * x * x + self.b * x + self.c
def rozwiaz(self):
if self.a == 0 and self.b == 0:
if self.c == 0:
return float("inf"), float("inf")
else:
return float("nan"), float("nan")
if self.a == 0:
return -self.c/self.b, -self.c/self.b
delta = self.b**2 - 4*self.a*self.c
if delta < 0:
return float("nan"), float("nan")
return (-self.b-math.sqrt(delta))/(2*self.a), (-self.b+math.sqrt(delta))/(2*self.a)
def main():
f1 = FunkcjaKwadratowa(2, 3, 1)
f1.wypisz()
print(f1.oblicz_wartosc(0))
print(f1.oblicz_wartosc(1))
print(FunkcjaKwadratowa(0, 0, 0).rozwiaz())
print(FunkcjaKwadratowa(0, 0, 1).rozwiaz())
print(FunkcjaKwadratowa(0, 2, 3).rozwiaz())
print(FunkcjaKwadratowa(1, 2, 3).rozwiaz())
print(FunkcjaKwadratowa(1, -5, 6).rozwiaz())
print(FunkcjaKwadratowa(1, 4, 4).rozwiaz())
if __name__ == "__main__":
main()
Liczba zespolona
To, co wprowadza nowego to zadanie, to głównie tworzenie i zwracanie jako wynik metody instancji naszej własnej klasy (zwracamy liczbę zespoloną w przypadku metod dodaj()
czy mnoz()
). Poza tym zwróćmy uwagę na wypisywanie liczby zespolonej: zwracamy uwagę na to, że część urojona może być ujemna i żeby zabezpieczyć się przed potworkami w stylu 2+-3i
rozbijamy metodę na dwa przypadki. Cała reszta jest raczej samowyjaśniająca się. W mnoz()
przypisaliśmy poszczególne składowe do łatwiejszych w operowaniu zmiennych jednoliterowych. To nie taka rzadka praktyka przy operowaniu na wzorach matematycznych: aby zwiększyć czytelność i zminimalizować możliwość pomyłki, rozbijamy obliczenia na części, których wyniki przypisujemy do zmiennych lub stosujemy aliasy, tak jak tutaj.
class Zespolona:
def __init__(self, re, im):
self.re = re
self.im = im
def wypisz(self):
if self.im < 0:
print(f"{self.re}{self.im}i")
else:
print(f"{self.re}+{self.im}i")
def modul(self):
return math.sqrt(self.re * self.re + self.im * self.im)
@staticmethod
def dodaj(z1, z2):
return Zespolona(z1.re + z2.re, z1.im + z2.im)
@staticmethod
def odejmij(z1, z2):
return Zespolona(z1.re - z2.re, z1.im - z2.im)
# (a+bi) * (c+di) = ac-bd + (bc+ad)i
@staticmethod
def mnoz(z1, z2):
a = z1.re
b = z1.im
c = z2.re
d = z2.im
return Zespolona(a*c-b*d, b*c+a*d)
def main():
z1 = Zespolona(3, 4)
z2 = Zespolona(2, 6)
z1.wypisz()
print(z1.modul())
Zespolona.dodaj(z1, z2).wypisz()
Zespolona.odejmij(z1, z2).wypisz()
Zespolona.mnoz(z1, z2).wypisz()
if __name__ == "__main__":
main()
Ułamek
Zacznijmy od skracania. Aby skrócić ułamek, należy znaleźć największy wspólny dzielnik (po polsku “nwd”, a po angielsku “gcd”) licznika i mianownika. Następnie dzielimy licznik i mianownik przez znaleziony największy wspólny dzielnik. Całe szczęście w Pythonie zostało zaimplementowane znajdowanie największego wspólnego dzielnika w funkcji gcd()
w pakiecie math
. Musimy dokonać dzielenia całkowitoliczbowego (//
zamiast /
). Nie dlatego, jak poprzednio, aby zaokrąglić wynik do liczby całkowitej, bo wiemy, że w wyniku nie będziemy mieli części ułamkowej. Chcemy, aby dane przechowywane w polach klasy były typu int
, a nie float
, ponieważ w przeciwnym wypadku nie uda nam się wywołać ponownie, w przyszłości, gcd()
, gdyż funkcja ta sprawdza, czy aby na pewno działa na typie int
.
W każdym działaniu upewniamy się, że zwracamy skrócony ułamek. Staramy się, aby użytkownik, o ile sam nie stworzy takiego obiektu, nie dostawał w wyniku działania funkcji nieskróconego ułamka. W dodaj()
i odejmij()
posiłkujemy się zmiennymi pomocniczymi, aby obliczyć liczniki, gdy sprowadzimy ułamki do wspólnego mianownika. To znów ma na celu zwiększenie czytelności oraz zminimalizowanie szansy na błąd.
class Ulamek:
def __init__(self, licznik, mianownik):
self.licznik = licznik
self.mianownik = mianownik
def wypisz(self):
print(f"({self.licznik})/({self.mianownik})")
def skroc(self):
nwd = math.gcd(self.licznik, self.mianownik)
self.licznik //= nwd
self.mianownik //= nwd
@staticmethod
def dodaj(u1, u2):
lewy_licznik = u1.licznik * u2.mianownik
prawy_licznik = u2.licznik * u1.mianownik
wynik = Ulamek(lewy_licznik + prawy_licznik, u1.mianownik * u2.mianownik)
wynik.skroc()
return wynik
@staticmethod
def odejmij(u1, u2):
lewy_licznik = u1.licznik * u2.mianownik
prawy_licznik = u2.licznik * u1.mianownik
wynik = Ulamek(lewy_licznik - prawy_licznik, u1.mianownik * u2.mianownik)
wynik.skroc()
return wynik
@staticmethod
def mnoz(u1, u2):
wynik = Ulamek(u1.licznik * u2.licznik, u1.mianownik * u2.mianownik)
wynik.skroc()
return wynik
@staticmethod
def dziel(u1, u2):
wynik = Ulamek(u1.licznik * u2.mianownik, u1.mianownik * u2.licznik)
wynik.skroc()
return wynik
def main():
u1 = Ulamek(3, 4)
u2 = Ulamek(2, 6) # nieskrocony
u1.wypisz()
u2.wypisz()
u2.skroc()
u2.wypisz()
print("Dodawanie")
Ulamek.dodaj(u1, u2).wypisz()
print("Odejmowanie")
Ulamek.odejmij(u1, u2).wypisz()
print("Mnozenie")
Ulamek.mnoz(u1, u2).wypisz()
print("Dzielenie")
Ulamek.dziel(u1, u2).wypisz()
if __name__ == "__main__":
main()



