8. hét: osztályok

Czirkos Zoltán, Frey Balázs · 2023.10.26.

Osztályok definíciója, példányosítása. Paraméterátadás.

1. Összetett adatszerkezet

Az itt látható táblázat egy futóverseny eredményeit tartalmazza.

IndexNévSzületésHelyezés
0Am Erika1994. 05. 06.1
1Break Elek2001. 09. 30.3
2Dil Emma1998. 08. 25.2
3Kasza Blanka1989. 06. 10.5
4Reset Elek2001. 04. 05.4

Alább egy elkezdett programot látsz, amelyben a megfelelő típusok már definiálva vannak, és az adatokat egy lista tartalmazza. Egészítsd ki a programot, hogy kiírja a képernyőre a kommentekben megadott adatokat!

class Datum:
    def __init__(self, ev, honap, nap):
        self.ev = ev
        self.honap = honap
        self.nap = nap

class Versenyzo:
    def __init__(self, nev, szuletes, helyezes):
        self.nev = nev
        self.szuletes = szuletes
        self.helyezes = helyezes

def datum_str(d):
    pass    # később kiegészítendő

def versenyzo_str(v):
    pass    # később kiegészítendő

def main():
    versenyzok = [
        Versenyzo("Am Erika", Datum(1994, 5, 6), 1),
        Versenyzo("Break Elek", Datum(2001, 9, 30), 3),
        Versenyzo("Dil Emma", Datum(1998, 8, 25), 2),
        Versenyzo("Kasza Blanka", Datum(1989, 6, 10), 5),
        Versenyzo("Reset Elek", Datum(2001, 4, 5), 4),
    ];

    # 0-s versenyző neve
    # 2-es versenyző helyezése
    # 4-es versenyző születési dátuma (írd meg a datum_str függvényt!)
    # 1-es versenyző nevének kezdőbetűje
    # az 1-es versenyző dobogós-e? igen/nem
    # az 4-es versenyző gyorsabb-e, mint a 3-as versenyző?
    # az 1-es versenyző ugyanabban az évben született-e, mint a 2-es?
    # egészítsd ki a versenyzo_str() függvényt, és írd ki az 1-es versenyző adatait
    # végül listázd ki az összes versenyzőt sorszámozva, összes adatukkal.

main()
Megoldás
class Datum:
    def __init__(self, ev, honap, nap):
        self.ev = ev
        self.honap = honap
        self.nap = nap

class Versenyzo:
    def __init__(self, nev, szuletes, helyezes):
        self.nev = nev
        self.szuletes = szuletes
        self.helyezes = helyezes

def datum_str(d):
    return f"{d.ev}. {d.honap:02}. {d.nap:02}."

def versenyzo_str(v):
    return f"Nev: {v.nev}\nSzületési dátum: {datum_str(v.szuletes)}\nHelyezés: {v.helyezes}\n"

def main():
    versenyzok = [
        Versenyzo("Am Erika", Datum(1994, 5, 6), 1),
        Versenyzo("Break Elek", Datum(2001, 9, 30), 3),
        Versenyzo("Dil Emma", Datum(1998, 8, 25), 2),
        Versenyzo("Kasza Blanka", Datum(1989, 6, 10), 5),
        Versenyzo("Reset Elek", Datum(2001, 4, 5), 4),
    ];

    # 0-s versenyző neve
    print("0. versenyző neve:", versenyzok[0].nev)

    # 2-es versenyző helyezése
    print("2. versenyző helyezése:", versenyzok[2].helyezes)

    # 4-es versenyző születési dátuma (írd meg a datum_str függvényt!)
    print("4. versenyző születési dátuma:", datum_str(versenyzok[4].szuletes))

    # 1-es versenyző nevének kezdőbetűje
    print("1. versenyző nevének kezdőbetûje:", versenyzok[1].nev[0])

    # az 1-es versenyző dobogós-e? igen/nem
    if versenyzok[1].helyezes <= 3:
        print("Az 1. versenyző dobogós lett")
    else:
        print("Az 1. versenyző nem lett dobogós")

    # az 4-es versenyző gyorsabb-e, mint a 3-as versenyző?
    if versenyzok[4].helyezes < versenyzok[3].helyezes:
        print("A 4. versenyző gyorsbb volt, mint a 3. versenyző")
    else:
        print("A 4. versenyző nem volt gyorsabb, mint a 3. versenyző")

    # az 1-es versenyző ugyanabban az évben született-e, mint a 2-es?
    if versenyzok[1].szuletes.ev == versenyzok[2].szuletes.ev:
        print("Az 1. versenyző ugyanabban az évben született, mint a 2. versenyző")
    else:
        print("Az 1. versenyző más évben született, mint a 2. versenyző")

    # egészítsd ki a versenyzo_str() függvényt, és írd ki az 1-es versenyző adatait
    print("Az 1. versenyző adatai:", versenyzo_str(versenyzok[1]))

    # végül listázd ki az összes versenyzőt sorszámozva, összes adatukkal.
    print("A versenyzők adatai:")
    for i in range(len(versenyzok)):
        print(f"A(z) {i}. versenyző adatai:\n" + versenyzo_str(versenyzok[i]))

main()

Korábbi előadáson már láttuk, hogy a for() ciklusban a ciklusváltozó nem az indexeket vesz fel értékül, hanem tényleges elemeket. Ha mégis szükségünk lenne az indexre, while() ciklust használtunk, vagy a mintamegoldásban használt range(len(lista)) trükköt. Van azonban egy másik megoldás, ahol két ciklus változónk van, az első az indexet, a második az elem értékét (tehát lista[i]-t) veszi fel:

for i, v in enumerate(versenyzok): #i==0 v==versenyzok[0]
    print(f"A(z) {i}. versenyző adatai:", versenyzo_str(v))

Erről később még lesz szó előadáson.

2. Az __str__(self) függvény

Az előadáson szerepelt, hogy az osztály belsejében definiált __str__ függvénnyel „meg lehet tanítani” a Pythonnak, hogy egy adott típus hogyan konvertálható sztringgé.

Módosítsd az előző programot úgy, hogy a datum_str() és a versenyzo_str() függvények helyett __str__ függvényeket kapnak az osztályok! Ezek után pedig, írd át a főprogramot, ahol kell.

Megoldás
class Datum:
    def __init__(self, ev, honap, nap):
        self.ev = ev
        self.honap = honap
        self.nap = nap

    def __str__(self):
        return f"{self.ev}. {self.honap:02}. {self.nap:02}."

class Versenyzo:
    def __init__(self, nev, szuletes, helyezes):
        self.nev = nev
        self.szuletes = szuletes
        self.helyezes = helyezes

    def __str__(self):
        return f"Nev: {self.nev}\nSzületési dátum: {self.szuletes}\nHelyezés: {self.helyezes}\n"

def main():
    versenyzok = [
        Versenyzo("Am Erika", Datum(1994, 5, 6), 1),
        Versenyzo("Break Elek", Datum(2001, 9, 30), 3),
        Versenyzo("Dil Emma", Datum(1998, 8, 25), 2),
        Versenyzo("Kasza Blanka", Datum(1989, 6, 10), 5),
        Versenyzo("Reset Elek", Datum(2001, 4, 5), 4),
    ];

    # 0-s versenyző neve
    print("0. versenyző neve:", versenyzok[0].nev)

    # 2-es versenyző helyezése
    print("2. versenyző helyezése:", versenyzok[2].helyezes)

    # 4-es versenyző születési dátuma
    print("4. versenyző születési dátuma:", versenyzok[4].szuletes)

    # 1-es versenyző nevének kezdőbetűje
    print("1. versenyző nevének kezdőbetûje:", versenyzok[1].nev[0])

    # az 1-es versenyző dobogós-e? igen/nem
    if versenyzok[1].helyezes <= 3:
        print("Az 1. versenyző dobogós lett")
    else:
        print("Az 1. versenyző nem lett dobogós")

    # az 4-es versenyző gyorsabb-e, mint a 3-as versenyző?
    if versenyzok[4].helyezes < versenyzok[3].helyezes:
        print("A 4. versenyző gyorsbb volt, mint a 3. versenyző")
    else:
        print("A 4. versenyző nem volt gyorsabb, mint a 3. versenyző")

    # az 1-es versenyző ugyanabban az évben született-e, mint a 2-es?
    if versenyzok[1].szuletes.ev == versenyzok[2].szuletes.ev:
        print("Az 1. versenyző ugyanabban az évben született, mint a 2. versenyző")
    else:
        print("Az 1. versenyző más évben született, mint a 2. versenyző")

    # írd ki az 1-es versenyző adatait
    print("Az 1. versenyző adatai:", versenyzok[1])

    # végül listázd ki az összes versenyzőt sorszámozva, összes adatukkal.
    print("A versenyzők adatai:")
    for i in range(len(versenyzok)):
        print(f"A(z) {i}. versenyző adatai:\n{versenyzok[i]}")

main()

3. Időpontok

Írj programot, amely egy osztályban időpontot tárol: óra, perc. Írjunk függvényeket ehhez:

  • ido_kiir(i): kiírja az időpontot óra:perc formában.
  • ido_hozzaad(i, p): hozzáad p percet az i időponthoz, és visszatér az új időponttal. Pl. 15:15 + 45 = 16:00.
  • ido_eltelt(i1, i2): megmondja, hány perc telt el a két időpont között, pl. 16:30-15:15 = 75 perc. (A paraméterek sorrendje a kivonásnál megszokott: kisebbítendő, kivonandó.)
  • ido_kivon(i, p): kivon p percet az i időpontból, és visszatér az új időponttal. Pl. 15:45 - 30 = 15:15.
Tipp

A feladat megoldása közben gyakran kell 60 perces átváltást csinálni, pl. 15:15 + 70 = 15:85 = 16:25. Érdemes olyan konstruktort írni az osztálynak, amely órát és percet vesz át paraméterként, és ezt az átváltást megcsinálja – ahogy az előadáson is szerepelt a tört osztály és az egyszerűsítés kapcsán. Akkor nem kell majd több helyen is foglalkozni ezzel, duplikálni a kódot.

Megoldás

A 60 perc és a 24 óra kezelésére a percek hozzáadásánál nagyon jól használható a maradékképzés. Pl. 16:55+10 esetén: :55+10 = :65, ami helyett a következő óra :05 kellene. 65%60, vagyis a 60-nal osztás maradéka pont a percet adja, 65/60, maga az osztás pedig 1-et, amennyivel az órát meg kell növelni. Az órát utána egyszerűen 24-gyel modulózzuk, mert a napokkal már nem kell foglalkozni.

A kivonást ugyanígy lehet kezelni.

class Idopont:
    def __init__(self, ora, perc):
        self.ora = ora
        self.perc = perc


def ido_kiir(i):
    print(f"{i.ora:2}:{i.perc:02}")


def ido_hozzaad(i, p):
    novelt = i.perc+p
    i.perc = novelt%60
    i.ora += novelt//60
    i.ora %= 24
    return i


def ido_eltelt(i1, i2):
    return (i1.ora - i2.ora)*60 + (i1.perc - i2.perc)


def ido_kivon(i, p):
    csokkentett = i.perc-p
    i.perc = csokkentett%60
    i.ora += csokkentett//60
    i.ora %= 24
    return i


def main():
    i = Idopont(22, 2)
    ido_kiir(i) # 22:02
    i = ido_hozzaad(i, 40)
    ido_kiir(i) # 22:42
    i = ido_hozzaad(i, 20)
    ido_kiir(i) # 23:02
    i = ido_hozzaad(i, 60)
    ido_kiir(i) #  0:02

    print(ido_eltelt(Idopont(20, 00), Idopont(14, 30)))

    i = Idopont(0, 2)
    ido_kiir(i) #  0:02
    i = ido_kivon(i, 60)
    ido_kiir(i) # 23:02
    i = ido_kivon(i, 20)
    ido_kiir(i) # 22:42
    i = ido_kivon(i, 40)
    ido_kiir(i) # 22:02


main()

4. Kerítés

Az előadásban szerepelt a pont típus. Ebben a feladatban ezzel kell megoldanod egy problémát.

Add meg a Pont típust, amely kétdimenziós koordinátát (x, y) tárol! Írj ehhez függvényeket:

  • tav(): a paraméterként kapott két pont távolságával tér vissza! (Ehhez Pitagorasz tételét kell használni.)
  • egyenlo(): megvizsgál két pontot, és megmondja, hogy egybeesnek-e.
  • beolvas(): beolvassa egy pont koordinátáit a billentyűzetről, és visszatér vele.

Ha ezek megvannak, az eddigiek használatával oldd meg az alábbi feladatot:

Kerítés hossza

Egy gazda szeretné körbekeríteni a telkét drótkerítéssel. Írj programot, amely kiszámítja, hogy mennyi kerítésre lesz szüksége! A program kérje egymás után a kerítésoszlopok koordinátáit (x, y koordinátapárok), számítsa ki az aktuális és az előző oszlop távolságát, és összegezze a távolságokat! Az összegzést addig folytassa, amíg a megadott koordináták nem egyeznek az elsőként megadott koordinátapárral, vagyis míg vissza nem ér a kezdőoszlophoz!

Ehhez célszerű egy változóba följegyezni a kezdőpontot, azután pedig két további, pont típusú változóval dolgozni: az egyik tárolja az új pont adatait, a másik pedig mindig az eggyel előzőt.

Megoldás

A megoldáshoz nem kell listát használni: mindig csak az előző koordinátára kell emlékezni, és azzal az új kerítésszakasz hossza kiszámítható. Hasonlóképp, az első oszlop koordinátáit eltárolva észre tudja venni a program, amikor a legutóbbi beírt pont egybeesik az elsővel.

import math

class Pont:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def tav(p1, p2):
    return math.sqrt((p1.x-p2.x)**2 + (p1.y-p2.y)**2)

def egyenlo(p1, p2):
    return p1.x==p2.x and p1.y==p2.y

def beolvas():
    x = float(input("X koordináta: "))
    y = float(input("Y koordináta: "))
    return Pont(x,y)

def main():
    elso = elozo = beolvas()
    kovetkezo = beolvas()
    hossz = 0
    while not egyenlo(kovetkezo, elso):
        hossz += tav(kovetkezo, elozo)
        elozo = kovetkezo
        kovetkezo = beolvas()
    hossz += tav(kovetkezo, elozo)

    print("A kerítés hossza:", hossz)

main()

5. Szállóvendégek

Egy hétemeletes szállodában a szobafoglalásokat listában tárolják. A szobák a szokásos módon vannak számozva, a százasok adják meg az emeletet, a többi pedig a szoba sorszámát (pl. 712 = 7. emelet, 12. szoba). A földszint a 0. szint, utána 1-től 7-ig az emeletek. Ennél a feladatnál nem kell teljes programot írni, csak a megadott részeket.

  • Definiálj Vendeg nevű típust, amelyik egy szállóvendég adatait (név: sztring, szobaszám: egész) tartalmazza! Írj függvényt, amely átvesz egy vendéget, és visszaadja, hogy melyik emeleten lakik!
  • Írj függvényt, amely átvesz egy Vendeg elemekből álló listát és egy nevet! Keresse ez meg a névhez tartozó foglalást és adja vissza a megtalált listaelemet vagy None-t, ha nincs találat!
  • Írj függvényt, amely paraméterként kapja a vendégek listáját és visszaad egy másik listát, amelyet a szint sorszámával indexelünk! Írja be az utóbbi listába, hogy az egyes emeleteken hány vendég lakik! Ha van üres emelet, annak is szerepelnie kell ebben.
  • Írj függvényt, amely megkapja a vendégek listáját, az előző függvénnyel előállítja a betöltöttségek listáját, és végül visszatér a legzsúfoltabb emelet sorszámával – tehát azzal, ahol a legtöbb vendég van éppen!
  • Írj főprogramot, amelyben létrehozod a listát az alábbi foglalásokkal, és megkeresed a legzsúfoltabb emeletet!
NévSzoba
Dia Dóra712
Elektro M Ágnes713
Érték Elek506
Megoldás
class Vendeg:
    def __init__(self, nev, szobaszam):
        self.nev = nev
        self.szobaszam = szobaszam
 
# v: Vendeg
# vissza: int
def hanyadik_emeleten(v):
    return v.szobaszam // 100
 
# foglalasok: [Vendeg, Vendeg, Vendeg, ...]
# nev: "Minta Áron"
# vissza: Vendeg
def vendeg_keres(foglalasok, nev):
    for i in range(0, len(foglalasok)):
        if nev == foglalasok[i].nev:
            return foglalasok[i]
    return None
    
    # Ez is teljesen jó megoldás
    # for v in foglalasok:
    #     if nev == v.nev:
    #         return v
    # return None
 
# foglalasok: [Vendeg, Vendeg, Vendeg, ...]
# vissza: [0, 1, 12, 7, 0, 5, 8, 3]
def melyik_emeleten_hanyan(foglalasok):
    hanyan = [0] * 8
    for i in range(len(foglalasok)):
        v = foglalasok[i]           # Vendeg
        e = hanyadik_emeleten(v)    # int
        hanyan[e] += 1
    return hanyan
    
    # Ez is teljesen jó megoldás
    # hanyan = [0] * 8
    # for v in foglalasok:
    #     hanyan[hanyadik_emeleten(v)] += 1
    # return hanyan
 
# foglalasok: [Vendeg, Vendeg, ...]
# vissza: int
def legzsufoltabb(foglalasok):
    hanyan = melyik_emeleten_hanyan(foglalasok) # [0, 1, 12, 7, ...]
    maxidx = 0
    for i in range(1, len(hanyan)):
        if hanyan[i] > hanyan[maxidx]:
            maxidx = i
    return maxidx

# Ezt nem kérte a feladat, csak a teszt kedvéért
def main():
    foglalasok = [
        Vendeg("Dia Dóra", 712),
        Vendeg("Elektro M Ágnes", 713),
        Vendeg("Érték Elek", 506),
    ]
    v = vendeg_keres(foglalasok, "Dia Dóra")
    print("Vendég szobája:", v.szobaszam)
    print("Hányadikon:", hanyadik_emeleten(v))
    
    print("Betöltöttség:", melyik_emeleten_hanyan(foglalasok))
    print("Legzsúfoltabb:", legzsufoltabb(foglalasok))
    

main()

6. Charlie

Charlie fagylaltot árul: jelenleg pisztácia, vanília, tutti-frutti, karamell, rumos dió és kávé a választék, de lehetne többféle is. A fagyit íz alapján kérhetik a gyerekek, egyszerre csak egy gombócot. A készlet véges, minden gombóc eladásával értelemszerűen eggyel csökken.

Definiálj osztályt, ami egy fagyi adatait (íz, hány gombóc van) tárolja! Írj függvényt, amely a fagyi objektumok listáját kapja és egy ízt; vissza pedig a megtalált elem referenciáját adja, vagy None-t!

Egészítsd ki ezt teljes programmá, amely a vásárlásokat kezeli! Hozz létre egy fagyi listát, és töltsd fel adatokkal. Olvasd be a vásárlásokat (ízeket) fájl vége jelig. Keresd meg az előbb megírt függvénnyel a kapott ízt, és jelezd a vásárlás eredményét: sikeres, kifogyott (volt, de 0-ra csökkent), nem is volt!

ÍzMennyiség
pisztácia0
vanília3
tutti-frutti8
karamell4
rumos dió5
kávé9