Bevezetés az R programozási nyelvbe 4. Vezérlési szerkezetek

Az R nyelven megírt algoritmusokban szereplő kódok alapvetően sorról sorra haladva, egymás után hajtódnak végre. Bizonyos feladatok azonban megkívánják, hogy egy-egy utasítás végrehajtását feltételekhez kössük, vagy a kód valamely részletét többször megismételve lefuttassuk. Ezt a célt szolgálják a vezérlési szerkezetek.

A Bevezetés az R programozási nyelvbe sorozat írásai egymásra épülnek. Aki most ismerkedik az R-rel, annak erősen ajánlom, hogy a korábbi blogposztokkal kezdje. A sorozat eddig elkészült tagjai:
  1. Vektorok
  2. Adatkeretek
  3. Listák
  4. Vezérlési szerkezetek

Az alábbiakban R nyelven (v4.1.2) írt kódot használok a feladat végrehajtásához. A magyarázó szövegek közé ékelt fekete kódblokkok tartalmát az RStudio-ban egymás alá illesztve elvileg bárki által reprodukálható az itt bemutatott műveletsor. A kódblokkok # kezdetű sorai pusztán magyarázó funkcióval bírnak, ezekre a program futtatásakor nincs szükség.

Feltételes utasítások (if … else)

A programozás során gyakran előfordul, hogy egy kódrészletet csak abban az esetben akarunk végrehajtani, ha teljesül valamilyen általunk meghatározott feltétel. Ha nem teljesül, akkor az adott kódrészlet lefuttatása elmarad, illetve más kódrészlet kerül helyette végrehajtásra.

A feltételes utasítások esetében tulajdonképpen egy döntési szituáció elé állítjuk az éppen futó programot. Előre megadunk bizonyos feltételeket és ezeknek megfelelő folytatási lehetőségeket. A programunknak tehát többféle kimenetele lehet, attól függően, hogy az aktuális feltételrendszer kiértékelése során milyen eredményt kapunk.

Nézzük meg mindezt a gyakorlatban is! Ehhez elöljáróban létrehozok két vektort, amelyekben az Európai Unió és a NATO tagállamai vannak felsorolva.

EU <- c("Ausztria", "Belgium", "Bulgária", "Ciprus", "Csehország", "Dánia", "Észtország", "Finnország", "Franciaország", "Görögország", "Hollandia", "Horvátország", "Írország", "Lengyelország", "Lettország", "Litvánia", "Luxemburg", "Magyarország", "Málta", "Németország", "Olaszország", "Portugália", "Románia", "Spanyolország", "Svédország", "Szlovákia", "Szlovénia")
length(EU)
## [1] 27
NATO <- c("Albánia", "Amerikai Egyesült Államok", "Belgium", "Bulgária", "Csehország", "Dánia", "Egyesült Királyság", "Észak-Macedónia", "Észtország", "Franciaország", "Görögország", "Hollandia", "Horvátország", "Izland", "Kanada", "Lengyelország", "Lettország", "Litvánia", "Luxemburg", "Magyarország", "Montenegró", "Németország", "Norvégia", "Olaszország", "Portugália", "Románia", "Spanyolország", "Szlovákia", "Szlovénia", "Törökország")
length(NATO)
## [1] 30

Az alábbi kódrészletben az eldöntendő kérdés az lesz, hogy egy konkrét állam tagja-e az EU-nak? Ha a válasz igen, akkor szöveges megerősítést kapunk az adott ország tagságáról. Ha nem, akkor egyelőre nem történik semmi.

Egy feltételes utasítás alapesetben így néz ki: if (feltétel) {kódblokk} Ha a feltétel értéke TRUE, akkor végrehajtódik a kódblokk tartalma, ha FALSE, akkor viszont nem. Ezeket a logikai értékeket megadhatjuk direktben is, de sokkal gyakoribb, hogy egy logikai kifejezéssel állítjuk elő őket.

tesztOrszag <- "Magyarország"
if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "tagja az EU-nak."))
}
## [1] "Magyarország tagja az EU-nak."
tesztOrszag <- "Szerbia"
if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "tagja az EU-nak."))
}

Ahogy látható, a nem EU tag Szerbia esetében nem történt semmi. Lehetőségünk van azonban arra, hogy egy “egyébként” (else) ágat is beiktassunk a feltételes utasításba. Ennek a kódblokknak a tartalma akkor hajtódik végre, ha a feltétel nem teljesül. A kód szintaxisa most a következő: if (feltétel) {kódblokk} else {kódblokk}

tesztOrszag <- "Szerbia"
if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "tagja az EU-nak."))
} else {
  print(paste(tesztOrszag, "nem tagja az EU-nak."))
}
## [1] "Szerbia nem tagja az EU-nak."

Egy utasításon belül több feltétel is megvizsgálható: if (feltétel) {kódblokk} else if (feltétel) {kódblokk} else {kódblokk} Ebben az esetben akárhány “egyébként ha” (else if) ágat megadhatunk, a végén lévő “egyébként” ág viszont opcionális. Amennyiben elhagyjuk és nem teljesül feltételek egyike sem, akkor nem történik semmi.

tesztOrszag <- "Albánia"
if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "tagja az EU-nak."))
} else if (tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "tagja a NATO-nak."))
}
## [1] "Albánia tagja a NATO-nak."
tesztOrszag <- "Magyarország"
if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "tagja az EU-nak."))
} else if (tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "tagja a NATO-nak."))
}
## [1] "Magyarország tagja az EU-nak."

Látható, hogy bár Magyarország tagja az EU-nak és a NATO-nak is, de csak EU tagként jelenik meg. Ennek oka a következő:

A több ágat tartalmazó feltételes utasításnál mindig az a kódblokk hajtódik végre, amelyiknél a feltétel elsőként teljesül. A feltételrendszer megfogalmazásánál érdemes alaposan átgondolni a szóba jöhető lehetőségeket és azok sorrendjét.

Logikai operátorokkal összekapcsolva olyan logikai kifejezéseket írhatunk, amelyek egyszerre több szempontot is figyelembe vesznek. Mondjuk azt, hogy egy ország tagja-e mindkét szervezetnek. Nézzünk minden eshetőségre egy-egy példát! (Ez ebben a formában, a kód többszöri leírásával nem túl elegáns megoldás. Később majd finomítunk rajta.)

tesztOrszag <- "Magyarország"
if (tesztOrszag %in% EU & tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "tagja az EU-nak és a NATO-nak is."))
} else if (tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "csak a NATO-nak a tagja."))
} else if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "csak az EU-nak a tagja."))
} else {
  print(paste(tesztOrszag, "nem tagja egyik szervezetnek sem."))
}
## [1] "Magyarország tagja az EU-nak és a NATO-nak is."
tesztOrszag <- "Amerikai Egyesült Államok"
if (tesztOrszag %in% EU & tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "tagja az EU-nak és a NATO-nak is."))
} else if (tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "csak a NATO-nak a tagja."))
} else if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "csak az EU-nak a tagja."))
} else {
  print(paste(tesztOrszag, "nem tagja egyik szervezetnek sem."))
}
## [1] "Amerikai Egyesült Államok csak a NATO-nak a tagja."
tesztOrszag <- "Ausztria"
if (tesztOrszag %in% EU & tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "tagja az EU-nak és a NATO-nak is."))
} else if (tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "csak a NATO-nak a tagja."))
} else if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "csak az EU-nak a tagja."))
} else {
  print(paste(tesztOrszag, "nem tagja egyik szervezetnek sem."))
}
## [1] "Ausztria csak az EU-nak a tagja."
tesztOrszag <- "Szerbia"
if (tesztOrszag %in% EU & tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "tagja az EU-nak és a NATO-nak is."))
} else if (tesztOrszag %in% NATO) {
  print(paste(tesztOrszag, "csak a NATO-nak a tagja."))
} else if (tesztOrszag %in% EU) {
  print(paste(tesztOrszag, "csak az EU-nak a tagja."))
} else {
  print(paste(tesztOrszag, "nem tagja egyik szervezetnek sem."))
}
## [1] "Szerbia nem tagja egyik szervezetnek sem."

A feltételes utasítások egymásba ágyazhatók. Tehát a kódblokkokban akár újabb feltételes utasítások is szerepelhetnek, ezáltal tovább árnyalva az adott feltételrendszer kínálta lehetőségeket.

Végezetül megemlítem még, hogy bizonyos esetekben az ifelse(feltétel, haIgaz, haHamis) függvényt is használhatjuk. Ekkor a függvény paramétereként kell megadni a magát a feltételt, valamint azt is, hogy ennek kiértékelése után mi történjen. Ezt a megoldást nyilván csak akkor érdemes alkalmazni, ha egy szimpla eldöntendő kérdésünk van. Egy bonyolultabb feltételrendszernél jobban járunk a fentebb elmondottakkal.

tesztOrszag <- "Szerbia"
print(paste(tesztOrszag, ifelse(tesztOrszag %in% EU, "taga az EU-nak.", "nem tagja az EU-nak.")))
## [1] "Szerbia nem tagja az EU-nak."

Ismétlések előre ismert számú alkalommal (for)

Igen gyakran előfordul, hogy bizonyos utasításokat több alkalommal is le akarunk futtatni. Hiszen végső soron a programozás lényege az, hogy automatizáljuk a feladatokat. Ilyenkor rendszerint más-más bemeneti értékekkel dolgozunk, s ezeknek megfelelően más-más kimenetet várunk, de maga a művelet lényege viszont azonos.

Ha előre el tudjuk dönteni, hogy hányszor akarjuk lefuttatni az adott utasítást, akkor a for (változó in vektor) {kódblokk} szintaxist kell használnunk. Itt a kódblokk tartalma annyi alkalommal fog lefutni, ahány eleme van a vektornak. Menet közben a változó mindig felveszi a vektor soron következő elemének megfelelő értéket, amire szükség esetén a kódblokkban hivatkozni tudunk.

Az ismétlések használatának értelmét demonstrálandó, gondoljunk vissza arra a fentebbi példára, amikor egy komplex feltételrendszer alapján az algoritmus eldöntötte, hogy egy-egy ország milyen kapcsolatban van az EU-val és/vagy a NATO-val. Ha megnézzük a kérdéses algoritmust, akkor láthatjuk, hogy az egyébként azonos feltételrendszert minden egyes futtatás alkalmával le kellett írni. Jelen esetben ezt szeretnénk kiküszöbölni.

Az alábbiakban egy vektorban adom meg a tesztelendő országokat. Ezen a vektoron végigmegy az algoritmus, és minden elemén elvégzi a tesztet.

orszagok <- c("Magyarország", "Amerikai Egyesült Államok", "Ausztria", "Szerbia", "Koszovó", "Svédország", "Albánia", "Görögország")
for (tesztOrszag in orszagok) {
  if (tesztOrszag %in% EU & tesztOrszag %in% NATO) {
    print(paste(tesztOrszag, "tagja az EU-nak és a NATO-nak is."))
  } else if (tesztOrszag %in% NATO) {
    print(paste(tesztOrszag, "csak a NATO-nak a tagja."))
  } else if (tesztOrszag %in% EU) {
    print(paste(tesztOrszag, "csak az EU-nak a tagja."))
  } else {
    print(paste(tesztOrszag, "nem tagja egyik szervezetnek sem."))
  }
}
## [1] "Magyarország tagja az EU-nak és a NATO-nak is."
## [1] "Amerikai Egyesült Államok csak a NATO-nak a tagja."
## [1] "Ausztria csak az EU-nak a tagja."
## [1] "Szerbia nem tagja egyik szervezetnek sem."
## [1] "Koszovó nem tagja egyik szervezetnek sem."
## [1] "Svédország csak az EU-nak a tagja."
## [1] "Albánia csak a NATO-nak a tagja."
## [1] "Görögország tagja az EU-nak és a NATO-nak is."

Az ismétléses ciklussal tehát jelentősen lerövidíthető egy kód, hiszen – a példánknál maradva – akkor is csak egyszer kellene leírni a feltételrendszert, ha a Föld összes országát be akarnánk vonni a tesztbe.

Ugyancsak gyakori alkalmazási területe a for ciklusnak, amikor egy táblázat sorain akarunk végigmenni azért, kiolvassuk, illetve feltöltsük annak valamely celláját.

Ennek kipróbálásához létrehozok egy adatkeretet két oszloppal. Az egyik oszlopba az EU országainak neve szerepel majd, a másikba egyelőre NA értékek kerülnek.

EU2 <- data.frame("orszagNev" = EU, "veletlenSzam" = as.numeric(NA))
print(EU2)
##        orszagNev veletlenSzam
## 1       Ausztria           NA
## 2        Belgium           NA
## 3       Bulgária           NA
## 4         Ciprus           NA
## 5     Csehország           NA
## 6          Dánia           NA
## 7     Észtország           NA
## 8     Finnország           NA
## 9  Franciaország           NA
## 10   Görögország           NA
## 11     Hollandia           NA
## 12  Horvátország           NA
## 13      Írország           NA
## 14 Lengyelország           NA
## 15    Lettország           NA
## 16      Litvánia           NA
## 17     Luxemburg           NA
## 18  Magyarország           NA
## 19         Málta           NA
## 20   Németország           NA
## 21   Olaszország           NA
## 22    Portugália           NA
## 23       Románia           NA
## 24 Spanyolország           NA
## 25    Svédország           NA
## 26     Szlovákia           NA
## 27     Szlovénia           NA

A feladat az, hogy soronként végigmenve a táblázaton, a veletlenSzam nevű oszlop adott cellájába bekerüljön egy 0 és 100 közötti, véletlen módon legenerált szám. A for ciklusban megadott vektor ebben az esetben az 1 és 27 közötti egész számokat tartalmazza – mivel annyi sora van az adatkeretnek, ahány tagállammal rendelkezik az EU. Az i nevű változó ezeket az értékeket fogja felvenni. Tehát mindig az adatkeretünk i-edik sorát manipuláljuk.

A for ciklusban szereplő változó, ha indexelésre használjuk, akkor általában az i vagy j nevet szokta kapni. Ez egy kódolási konvenció. Hogy honnan ered és mi értelme van, arról a vélemények megoszlanak... (Természetesen ettől minden további nélkül el lehet térni.)

A véletlenszámot a runif(n, min, max) függvénnyel állítom elő, amely a min és max paraméterekben megadott értékek között n darab számot generál le.

for (i in 1:nrow(EU2)) {
  EU2[i,"veletlenSzam"] <- runif(1, 0, 100)
}
print(EU2)
##        orszagNev veletlenSzam
## 1       Ausztria    83.963576
## 2        Belgium    77.047408
## 3       Bulgária    67.192006
## 4         Ciprus    24.483590
## 5     Csehország    40.290164
## 6          Dánia    83.532930
## 7     Észtország    33.406344
## 8     Finnország    34.927102
## 9  Franciaország    95.434810
## 10   Görögország    63.968418
## 11     Hollandia    88.640946
## 12  Horvátország    14.425536
## 13      Írország    60.211061
## 14 Lengyelország    12.717223
## 15    Lettország    58.056836
## 16      Litvánia    80.405957
## 17     Luxemburg    97.164880
## 18  Magyarország    25.499820
## 19         Málta     3.952774
## 20   Németország     8.140006
## 21   Olaszország    35.305240
## 22    Portugália    94.350614
## 23       Románia    38.761722
## 24 Spanyolország    81.080658
## 25    Svédország     2.751469
## 26     Szlovákia    66.459540
## 27     Szlovénia    32.631931

Ismétlések előre nem ismert számú alkalommal (while)

Előfordulhat az is, hogy egy utasítássorozatot addig akarunk ismételgetni, amíg egy bizonyos feltétel fennáll. Ennek szintaxisa a következő: while (feltétel) {kódblokk} Amennyiben a feltétel értéke TRUE, akkor végrehajtódik a kódblokk tartalma, ha FALSE, akkor nem. Ezeket a logikai értékeket direktben vagy egy logikai kifejezéssel tudjuk megadni.

Elképzelhető tehát, hogy az algoritmusunk igen sokáig fog futni. De az is lehetséges, hogy el sem indul, mert a megadott feltétel már eleve hamis.

Az alábbi algoritmus véletlenszerűen kiválaszt egy EU tagországot és megnézi, hogy az tagja-e a NATO-nak is. Amennyiben igen, akkor választ egy másikat. Ez addig ismétlődik, amíg nem talál egy olyan államot, amely csak az EU-nak a tagja.

Az algoritmust három alkalommal is lefuttatom annak érdekében, hogy lássuk, minden esetben más mennyiségű országot kaptunk.

# 1. próbálkozás

tesztOrszag <- sample(EU, 1)
while (tesztOrszag %in% NATO) {
  print(paste("Mindkét szövetségi rendszer tagja: ", tesztOrszag))
  tesztOrszag <- sample(EU, 1)
}
print(paste("Csak az EU tagja: ", tesztOrszag))
## [1] "Mindkét szövetségi rendszer tagja:  Belgium"
## [1] "Mindkét szövetségi rendszer tagja:  Dánia"
## [1] "Mindkét szövetségi rendszer tagja:  Németország"
## [1] "Mindkét szövetségi rendszer tagja:  Luxemburg"
## [1] "Mindkét szövetségi rendszer tagja:  Szlovákia"
## [1] "Mindkét szövetségi rendszer tagja:  Csehország"
## [1] "Csak az EU tagja:  Ausztria"
# 2. próbálkozás

tesztOrszag <- sample(EU, 1)
while (tesztOrszag %in% NATO) {
  print(paste("Mindkét szövetségi rendszer tagja: ", tesztOrszag))
  tesztOrszag <- sample(EU, 1)
}
print(paste("Csak az EU tagja: ", tesztOrszag))
## [1] "Mindkét szövetségi rendszer tagja:  Spanyolország"
## [1] "Mindkét szövetségi rendszer tagja:  Románia"
## [1] "Mindkét szövetségi rendszer tagja:  Németország"
## [1] "Csak az EU tagja:  Finnország"
# 3. próbálkozás

tesztOrszag <- sample(EU, 1)
while (tesztOrszag %in% NATO) {
  print(paste("Mindkét szövetségi rendszer tagja: ", tesztOrszag))
  tesztOrszag <- sample(EU, 1)
}
print(paste("Csak az EU tagja: ", tesztOrszag))
## [1] "Mindkét szövetségi rendszer tagja:  Luxemburg"
## [1] "Csak az EU tagja:  Ciprus"

A feltételes utasításokat és az ismétlődéseket természetesen tetszőleges mélységben egymásba ágyazhatjuk. Ez lehetővé teszi, hogy a programunk futtatása közben dinamikusan alkalmazkodjunk a változó körülményekhez és ezek figyelembevétele mellett állítsuk elő a végeredményt.

comments powered by Disqus