Vezérlési szerkezetek

Bevezető

Eddig olyan programokat tudunk írni, amelyek elindultak a belépési ponton, majd szép sorjában végigfutottak az összes utasításon. Nem voltak döntések vagy ismétlések. Szekvenciálisan futottak. Ebben a fejezetben megnézzük, hogyan írhatunk olyan programot, ami különböző feltételek teljesülésének függvényében más és más viselkedést produkál.

Kódtömbök

Mielőtt elmerülnénk a különböző vezérlési szerkezetekben, fontos, hogy egyszer és mindenkorra jól felfogjuk a különböző kódtömbök szerepét. Már szembesültünk vele, hogy a függvények törzsénél fontos, hogy azonos legyen a sorok betolása. Ez jelzi a fordítónak, hogy a sorok az adott függvény törzsét alkotják. Ezt nevezzük a kód indentálásának. Sok más programnyelvben a sorok eltolása nélkül is működik a program. Ott zárójelezéssel jelezzük, az összetartozó sorokat. Ennek ellenére más programnyelveknél is használjuk az indentálást, mert ettől átláthatóbbá válik a kód szerkezete. Pythonban nincs választás: indentálni kell. A verézlési szerkezetek is további indentálási szinteket fognak követelni. Ezek egymásba fognak ágyazódni.

Feltételes végrehajtás

A programokban lehetőség van arra, hogy bizonyos kódsorok csak egy feltétel teljesülése esetén hajtódjanak végre. "Ha a feltétel teljesül, csináld ezt!" A legtöbb programnyelvben ezt az úgynevett if kifejezések segítségével tudjuk megvalósítani. Ennek felépítése a következő:

                    
if <feltétel>:
    <kódtömb>
                    
                

A <feltétel> helyére egy logikai kifejezés kerül, ami igazra vagy hamisra értékelődik ki. Ez lehet egy egyszerű összehasonlító művelet, boolean változó vagy összetettebb zárójeleket, and és or-t tartalmazó kifejezés. A <kódtömb> bármilyen utasítások sorozata lehet. Az a fontos, hogy ezek négy szóközzel az if-es sortól beljebb kezdődjenek. Nézzünk egy példát:

                    
print('Ez az if előtt fut le.')
x = 1
if x < 10:
    print('Ez a feltétel teljesülésekor fut le.')
    print('Ez is csak a feltétel teljesülésekor fut le.')
print('Ez mindig lefut.')
                    
                

Hogyha valamilyen utasítást csak akkor szeretnénk végrehajtani, hogyha a korábban feltételhez kötött utasításaink nem hajtódnak végre, akkor az if kifejezés else ágát vesszük igénybe. Ez tehát azt csinálja, amit az angol szó sugall. Hogyha az if felétele nem teljesült, akkor ez a blokk kerül végrehajtásra. Élesben ez így néz ki:

                    
print('Ez az if előtt fut le.')
x = 10
if x < 10:
    print('Ez a feltétel teljesülésekor fut le.')
    print('Ez is csak a feltétel teljesülésekor fut le.')
else:
    print('Ez akkor fut, ha az if feltétele nem tejesül.')
    print('Ez is akkor fut, ha az if feltétele nem tejesül')
print('Ez mindig lefut.')
                    
                

Már tudunk olyan programot készíteni, ami két lehetőség közül választ. Mivan akkor, amikor több mint két lehetőség áll rendelkezésre? Ez sem lesz nehéz. Össszevonjuk az if-et és az else-t és íme, megszületett az elif! Ez ötvözi az előző kettő viselkedését. Rendelkezik feltétellel, ami akkor kerül kiértékelésre, ha az előző feltétel nem teljesült. Ezeket egymás után lehet fűzni. Annyi a lényeg, hogy először mindig egy if-nek kell lennie. Az után jöhetnek elif-ek. elif-ből lehet bármennyi. Végül lehet egy sima else. Sima else nem előzhet meg elif-et, mivel az a végső lehetőséget reprezentálja, ami akkor követezhet be, hogyha korábban semmelyik feltétel nem teljesült. Nézzünk példát!

                    
x = 15
if x < 10:
    print('x kisebb mint 10.')
elif x < 20:
    print('x kisebb mint 20.')
elif x < 30:
    print('x kisebb mint 30.')
else:
    print('x nagyobb vagy egyenlő mint 30.')
                    
                

Fontos tudatosítani, hogy az egy if után következő elif-ek sorozatából (és az egy else-ből) pontosan egy fog végrehajtódni. Hogyha az else-t lehadjuk, akkor lehet, hogy egysem. Méghozzá az az egy fog végrehajtódni, amelyiknek először teljesül a feltétele. Megfigyelhető, hogy ebben a példában egyre gyengébb felételeket fogalmaztam meg. Ugyanazek a felételek fordított sorrendben nem működtek volna. Hogyha először 30-al hasonlítottam volna össze az x-et, akkor a feltétel teljesült volna. Technikailag nem hibázna a program, hiszen 15 valóban kisebb, mint 30. Viszont az nem derülne ki, hogy az x értéke 20-tól is kisebb. A többi feltételt kiértékelés nélkül átugorná a program. Ennek intuitív megértését segíti, hogyha végig olvassuk a leírt kódot. if x < 10 ... elif x < 20 ... Ha x kisebb, mint 10, akkor írd ki ezt. Egyébként, hogyha igaz a következő feltétel, akkor csináld azt! Itt tehát az else / else if / elif-ben van a kulcsgondolat. Ezekkel csak akkor foglalkozik a progam, ha még egyik korábbi sem teljesült. Persze nem gond elérni, hogy minden ághoz tartozó feltétel külön kiértékelődjön. Egyszerűen ne használjuk az elif-et! Kezdjünk mindig új if-et! Valahogyan így:

                    
if x < 10:
    print('x kisebb mint 10.')
if x < 20:
    print('x kisebb mint 20.')
if x < 30:
    print('x kisebb mint 30.')
if x >= 30:
    print('x nagyobb vagy egyenlő mint 30.')
                    
                

A feltételek jó megfogalmazása nem egyszerű feladat. Még a tapasztalt programozók is szoktak néha hibát véteni.

Ciklusok

A feltételeket felhasználva már tudunk olyan programot írni, ami átugorja azokat az utasításokat, amelyekre az adott feltételek függvényében nincs szükség. Így lényegében előrefelé ugrálunk a kódban. Végrehajtunk feltételhez kötött részeket és kihagyunk más feltételhez kötötteket. A továbbiakban meg fogjuk nézni, hogyan lehet hátraugrani a kódban. Azaz visszaugrunk majd a kód egy korábbi pontjára, ahonnan újrakezdjük a végrehajtást. Az ilyen szerkezeteket ciklusoknak nevezzük.

While loop

Az első ciklus típus, amivel megismerkedünk a while ciklus vagy angolosan while loop lesz. Rendelkezik egy belépési feltétellel, amely teljesülése esetén ismétli a hozzátartozó utasításokat. A felépítése a következő:

                    
while \ltbelépési feltétel>:
    &lvégrehajtandó kód>
                    
                

Amikor programunk a while-t tartalmazó sorhoz ér, kiértékeli a <belépési feltétel>-ként megadott logikai kifejezést. Amennyiben ez igaz eredményt ad, a végrehajtás a while törzsében folytatódik. Ellenkező esetben egyszer sem fut le a while-ba írt kód. Eddig tehát úgy működik, mintha egy közönséges if-et írtunk volna. Hogyha viszont lefutott a ciklus törzse, akkor újra kiértékelődik a belépési feltételt. Hogyha a feltétel ismét teljesül, a while törzse újra végrehajtódik. Ez a működés mindaddig ismétlődik, amíg a belépési feltétel nem lesz hamis értékű. Nézzünk egy példát!

                    
i = 100
print('kezdődik a ciklus')
while i > 0:
    print(f'Az i értéke {i}')
    i -= 5
print('vége lett a ciklusnak')
                    
                

A programunk létrehoz egy i változót, amelyet 100-ra inicializál. A belépési feltételünk, hogy az i értéke legyen nagyobb, mint 0. Ez kezdetben természetesen teljesül. A while törzsében 5-ösével csökkentem az i értékét. Nézzük meg, hogy mi történik közvetlenül a ciklusból való kilépés előtt. Ott tartunk, hogy az i értéke éppen 5. Ez nagyobb, mint 0, ezért belépünk a while-ba. Itt kiírja a program az aktuális értéket, majd csökkenti az i értékét. Az új érték 0. Ezzel már nem teljesül a belépési feltétel. A végrehajtás a ciklus utáni kódsorral folytatódik. Nézzünk meg a következő egyszerű találós kérdés kódját.

                    
gondolt = 42
x = int(input('Találd ki, melyik számra gondoltam: '))
while x != gondolt:
    print(f'Nem a(z) {x} volt a gondolt szám.')
    x = int(input('Találd ki, melyik számra gondoltam: '))
print(f'Gratulálok! Valóban a {gondolt} volt a gondolt szám.')
                    
                

A fenti program egész számokat vár a felhasználótól. Hogyha eltaláljuk, hogy a 42-re "gondolt" a program, Akkor vége a ciklusnak és kapunk egy dicséretet. Érdemes kipróbálni. Szórakoztató tud lenni olyan... 1 percig.

Mi van akkor, hogyha a belépési feltétel soha nem lesz hamis? Az ilyen esetet nevezzük végtelen ciklusnak. A legtöbb esetben igyekszünk olyan feltételt megfogalmazni, amely egyszer biztosan hamissá válik. Az első példánkban bizonyíthatóan eljutunk egy nulla vagy negatív értékű i-hez. A másodikban már feltételezzük, hogy felhasználónk egyszer rájön a számra. Komolyabb alkalmazásban érdemes lenne lehetőséget biztosítani a játék feladására. A következő program tartalmaz egy végtelen ciklust.

                    
import time

num = 0
count_up = True
while True:
    print(num)
    if count_up:
        num += 1
        if num >= 10:
            count_up = False
    else:
        num -= 1
        if num <= 0:
            count_up = True
    time.sleep(1)
                    
                

A program először felfelé számol, majd tíznél visszafordul és lefelé kezd számolni. 0-tól ismét felfelé számol. Ezt ismétli a végtelenségig. Nem történik semmi gond, ha futtatjuk a programot, mivel kívülről is le lehet lőni a futását. PyCharmban kattintsunk a piros négyzetre!

For ciklus

A while ciklus bemutatására hozott példákban többször előfordult, hogy a ciklusban egy-egy változót kellett növelni vagy csökkenteni. Erre nagyon gyakran van szükség (és nem csak a most kitalált példákba lett készakarva beleerőltetve). Van is egy külön ciklus típus, ami leegyszerűsíti az ilyen iteratív feladatokat. Az olyan feladatokat, amikor egy intervallum elemein kell végiglépkedni. Ez a for ciklus. A felépítése a következő:

                    
for <ciklusváltozó> in <intervallum>:
    <ciklus törzse>
                    
                

A <ciklusváltozó>-t nem kell a for előtt létrehozni. Azzal, hogy szerepel a fejlécben, automatikusan létrejön és használhatóvá válik a ciklus törzsében. A ciklusváltozó az <intervallum>-ban található értékeket fogja sorban felvenni. Arról, hogy hogyan kell egy intervallumot definiálni lesz szó rövidesen. A törzs első futásakor a ciklusváltozó az intevallum első elemét tartalmazza. A további ismétlésekkor az intervallum soron következő eleme lesz a ciklusváltozó értéke. Ebből az is következik, hogy a for ciklus törzse pontosan annyiszor fog lefutni, ahány elemű az adott intervallum.

Mi az az intervallum? Igazából eddig azért használtam ezt a kifejezést, mert itt több minden szerepelhet. Az egyik lehetőség, hogy egy korábban létrehozott listát (list) adunk meg. Létrehozhatunk listát helyben is anélkül, hogy azt korábban egy változóhoz rendeltük volna.

                    
my_list = ['alma', 'körte', 'szilva']
for item in my_list:
    print(item)

# Vagy helyben definiálva:
for item in ['alma', 'körte', 'szilva']:
    print(item)
                    
                

A jövőben meg fogunk ismerkedni egyéb, a listához hasonló adatszerkezetekkel is. Ezekkel is hasonlóan fog működni a for.

Mi van akkor, ha nem egy konkrét listám van, hanem egyszerű egész számokon akarok iterálni. A while ciklus esetében is ilyeneket láttunk. Hogyan lehet ezt megoldani for segítségével?

range()

A range függvény számok felsorolását adja vissza eredményül. A kívánt intervallumot különböző függvény paraméterek megadásával specifikálhatjuk. A legegyszerűbb eset, hogyha csak egy számot adunk meg paraméterül. Ez az intervallumban tartozó elemek számát fejezi ki.

                    
                        for i in range(10):
                            print(i)
                    
                

A fenti példában a range függvény egy 10 elemű számsorozattal tér vissza. Az alapértelmezett viselkedés az, hogy a felsorolás első eleme a 0. Ez azt is jelenti, hogy az utolsó elem nem a 10 lesz, hanem a 9. Ez a viselkedés onnan ered, hogy az informatikában legtöbbször 0-tól kezdődik az indexelés. Korábban már beszéltünk például a lista elemeinek eléréséről. Ezt a lista neve utáni szögleteszárójelbe írt indexszel tehettük meg. Ott leírtam, hogy a lista első eleméhez a 0-ás index tartozik. Ennek oka a számítógépek memóriájának működésében keresendő. A Pythontól alacsonyabb szintű programozási nyelvekben a változók tárolhatnak nyers memória címeket. Hogyha a változó egy listát tárol, akkor valójában a lista első elemét tároló memória hely címét tárolja. Amikor a lista elemeit akarjuk elérni, akkor a megfelelő memória cím kiszámítása úgy működik, hogy a változóban tárolt címhez hozzáadjuk az indexet. Ezért az első elem indexe 0:

                    
// C nyelvben:
x = array[0];
// Ugyanaz mint:
x = *(array + 0);
                    
                

for ciklust néha szoktak úgy használni (különösen a Pythontól alacsonyabb szintű programokban ;) ), hogy egy i egész szám a ciklusváltozó, amelyet 0-tól kezdve növelünk, és a ciklus törzsében memória terület indexelésére használjuk. Remélhetőleg ez a magyarázat segít memorizálni, hogy a range miért 0-tól kezdődik.

A range függvénnyel lehetőség van nem nullával kezdődő intervallum megadására is. Ehhez két paramétert kell megadni. Az első szám az intervallum legkisebb eleme. A második az intervallum felső határa, de maga a szám nem tartozik az intervallumba. Például:

                    
range(3, 7)
                    
                

Az eredményül kapott számok a 3, 4, 5 és 6. A 7 már nem tartozik bele. További lehetőség, hogy ne csak egyesével növekedjenek a számok, hanem általunk definiált léptékben. Ezt egy harmadik paraméter megadásával adhatjuk meg. Hogyha például egy 2-vel kezdődő számsorozatot akarunk megadni, aminek legnagyobb eleme kisebb mint 100 és az értékek 4-esével növekednek, akkor ezt a következő felírással tehetjük meg:

                    
range(2, 100, 4)
                    
                

Próbáljuk ki for ciklussal:

                    
for i in range(2, 100, 4):
    print(i)
                    
                

Ebből látszik, hogy nem gond, hogyha az intervallum felső korlátja nem illesztkedik pontosan a tényleges legnagyobb értékhez. 98 lesz a legnagyobb szám a felsorolásban, mert ahhoz 4-et adva már 100 nál nagyobb egyenlő értéket kapnánk.

Egymásba ágyazott ciklusok

Ki mondta, hogy nem rakhatunk egy ciklusba még egy ciklust? Még szép, hogy rakhatunk! Erre sok összetettebb algoritmusban van szükség. Mutatok egy példát.

                    
lista = [45, 1, 5, 6, 78, 5, 23, 79, 46, 50, 40, 11, 12, 61, 80, 9, 7]
for i in range(len(lista)):
    for j in range(len(lista) - i - 1):
        if lista[j] > lista[j + 1]:
            temp = lista[j]
            lista[j] = lista[j + 1]
            lista[j + 1] = temp
print('Rendezett lista:')
print(lista)
                    
                

A fenti kód egy rendező algoritmus. Nagyvonalakban elmondva a belső ciklus végiglépked a listán, és hogyha két szomszédos elem rossz sorrendben van, akkor felcseréli azokat. A belső ciklus egyszeri lefutása biztosan a helyére rakja a lista legnagyobb elemét. Ezért a következő futáskor már nem kell elmenni csak az utolsó előtti elemig. A külső ciklus ezt tartja számon. Észrevehetjük, hogy a belső ciklus intervallumának felső korlátját a külső ciklus i változóját felhasználva számoljuk. Az algoritmus neve buborék rendezés. Ez arra utal, hogy a nagy értékek felbuborékoznak a lista végére. Megnyugtatásul elárulom, hogy Pythonban van kész rendező algoritmus. Ennek használatát a következő példa szemlélteti:

                    
lista = [45, 20, 1, 5, 6, 78, 5, 23, 79, 46, 50, 40, 11, 12, 61, 80, 9, 7]
lista.sort()
print('Rendezett lista:')
print(lista)      
                    
                
Összefoglaló

Ebben a fejezetben megismerkedtünk a különböző vezérlési szerkezetekkel. Képessé váltunk feltételek megfogalmazására if segítségével. Itt a feltétel nemteljesülése esetére alternatív viselkedést is adtunk meg az else és ifel szerkezetekkel. Megtanultuk, hogy hogyan írjunk while vagy for ciklust. Végül részletesebben átnéztük a for ciklus által használt intervallumok megadásának néhány módját. Erre az utóbbira nagyon sok módszer van. Itt most nem fedtünk le mindent. Például most nem beszéltünk arról, hogyan lehet visszafelé lépkedni egy listában. Viszont amennyit már tudunk, azzal a közeljövőben egész szép dolgokat fogunk tudni létrehozni.

Következő fejezet