Smyslem je vytvořit algoritmus, který vyřeší požadovanou úlohu. Dobré je se nejdříve zamyslet nad postupem, použít papír a tužku, a teprve pak začít tvořit program.
Logické operátory a operace == je rovno, != není rovno, < > <= >=, ! negace, & a zároveň, | nebo (pro všechny prvky srovnávaných vektorů), && a || porovnávají jen první prvek vektorů.
Operátory mají různé pořadí vyhodnocování (tzv. precedence operátorů). Podobně, jako * má vyšší přednost než +, tak & má vyšší přednost než |, oba mají ale vyšší přednost než např. ==. Pokud si nejsme jisti, je lepší závorkovat. Zvýší se tím i přehlednost zápisu.
identical() vrátí, zda jsou obsahy parametrů identické, xor() provádí exclusive-or operaci, which(vektor > 3) vrátí indexy prvků, splňujících podmínku (NA přeskočí), což je výhoda oproti pouhému samostatnému vektor > 3. all(vektor > 3) vrátí TRUE/FALSE, zda všechny prvky vektoru splňují podmínku, any(vektor > 3) vrátí, zda alespoň jeden prvek splňuje podmínku. is.element(co, vČem) nebo co %in% vČem vrátí, zda je prvek co obsažen ve vektoru vČem. all(kandidáti %in% vČem) ověří, že všichni kandidáti jsou součástí vČem. match(co, vČem) vrátí index první nalezené pozice.
Nápověda: ?‘if’
x <- 5
if (x == 0) {
print("x je 0.")
}
print("Pokračujeme.")
## [1] "Pokračujeme."
x <- 0
if (x == 0) {
print("x je 0.")
}
print("Pokračujeme.")
## [1] "x je 0."
## [1] "Pokračujeme."
a <- 5
b <- 3
if (b == 0) {
print("Nulou nelze dělit.")
} else {
a / b
}
## [1] 1.666667
Poznámka: else musí být na stejném řádku, jako znak }. V jiných jazycích je to jinak, ale R je R :-)
a <- 5
b <- 0
if (b == 0) {
print("Nulou nelze dělit.")
} else {
a / b
}
## [1] "Nulou nelze dělit."
x <- 5
if (x == 0) {
print('x je 0')
} else if (x < 10) {
print('x je menší než 10')
} else if (x < 20) {
print('x je menší než 20')
} else if (x >= 20 & x <= 30) {
print('x je mezi 20 a 30 (včetně)')
} else {
print('x je něco, co neřešíme')
}
## [1] "x je menší než 10"
Najde se první platná podmínka, ostatní se pak již netestují. Přestože je x zároveň menší než 10 i než 20, provede se jen první platná podmínka.
pozdrav <- "ahoj"
if (pozdrav == "ahoj") { # Porovnávání řetězců
print("Nazdar.")
} else {
print("Dobrý den.")
}
## [1] "Nazdar."
x <- 5
hodnota <- if (x > 3) { # Poněkud nezvyklá, ale užitečná konstrukce.
10
} else {
0
}
hodnota
## [1] 10
# Konstrukce ve stylu "Excel". Takhle je to pěkné, ale pro složitější větvení
# by to bylo značně nepřehledné.
x <- 5
hodnota <- ifelse(x > 3, 10, 0)
hodnota
## [1] 10
for (i in 1:8) {
cat("Dobrý den. ") # i nemusí být vůbec použito
}
cat("Hotovo.\n")
## Dobrý den. Dobrý den. Dobrý den. Dobrý den. Dobrý den. Dobrý den. Dobrý den. Dobrý den.
## Hotovo.
for (i in 1: 5) {
print(i) # příkaz se opakuje pro každé nové 'i'
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
print(i) # v i zůstává poslední hodnota 5 (což není v různých jazycích samozřejmé)
## [1] 5
Pozor na operátor dvojtečka : a jeho prioritu, která je vyšší než aritmetické operace (na rozdíl od např. jazyka Matlab)! Pokud se mají meze cyklu počítat vzorcem, výrazy je nutno závorkovat!
1:5
## [1] 1 2 3 4 5
1-1 : 5-1 # pravděpodobně dost nečekaný výsledek
## [1] -1 -2 -3 -4 -5
(1-1) : (5-1) # asi to, co jsme měli na mysli
## [1] 0 1 2 3 4
Pokud bychom závorky zapomněli, dostali bychom vskutku neočekávané výsledky, přičemž si chyby občas ani nemusíme všimnout.
for (i in 0: 10-1) {
print(i)
}
## [1] -1
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
jmena <- c("Tomáš", "Petr", "Ivo", "Hana", "Jana", "Tereza")
for (i in 1:length(jmena)) { # nebezpečné
jmeno <- jmena[i]
cat(i, ". prvek je ", jmeno, "\n", sep="")
}
## 1. prvek je Tomáš
## 2. prvek je Petr
## 3. prvek je Ivo
## 4. prvek je Hana
## 5. prvek je Jana
## 6. prvek je Tereza
jmena <- character(0)
for (i in 1:length(jmena)) { # nebezpečné
jmeno <- jmena[i]
cat(i, ". prvek je ", jmeno, "\n", sep="")
}
## 1. prvek je NA
## 0. prvek je
jmena <- c("Tomáš", "Petr", "Ivo", "Hana", "Jana", "Tereza")
for (i in seq(1, length(jmena))) { # nebezpečné
jmeno <- jmena[i]
cat(i, ". prvek je ", jmeno, "\n", sep="")
}
## 1. prvek je Tomáš
## 2. prvek je Petr
## 3. prvek je Ivo
## 4. prvek je Hana
## 5. prvek je Jana
## 6. prvek je Tereza
for (i in seq(1, length(jmena), by = 1)) { # lepší, ale může skončit s error
jmeno <- jmena[i]
cat(i, ". prvek je ", jmeno, "\n", sep="")
}
## 1. prvek je Tomáš
## 2. prvek je Petr
## 3. prvek je Ivo
## 4. prvek je Hana
## 5. prvek je Jana
## 6. prvek je Tereza
library(rPraat)
for (i in seqM(1, length(jmena))) { # bezpečné
jmeno <- jmena[i]
cat(i, ". prvek je ", jmeno, "\n", sep="")
}
## 1. prvek je Tomáš
## 2. prvek je Petr
## 3. prvek je Ivo
## 4. prvek je Hana
## 5. prvek je Jana
## 6. prvek je Tereza
for (i in seq_along(jmena)) { # v klidu
jmeno <- jmena[i]
cat(i, ". prvek je ", jmeno, "\n", sep="")
}
## 1. prvek je Tomáš
## 2. prvek je Petr
## 3. prvek je Ivo
## 4. prvek je Hana
## 5. prvek je Jana
## 6. prvek je Tereza
for (jmeno in jmena) { # v pohodě, akorát nemáme informaci o pořadí jmeno v jmena
cat(jmeno, "\n")
}
## Tomáš
## Petr
## Ivo
## Hana
## Jana
## Tereza
Problém s automatickým krokem operátoru : je nebezpečný, spousty matematický vzorců jsou psány jako opakování pro rozsah od 0 do N-1, což bychom zapsali 0 : (N-1)
Pozor na nutné závorky!
N <- 5
for (i in 0: (N-1)) {
print(i)
}
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
Ale co když bude N == 0? Pak bychom v našem automatickém algoritmu očekávali, že se for přeskočí, jako tomu je ve většině normálních programovacích jazyků. Ne však v R…
N <- 0
for (i in 0: (N-1)) {
print(i)
}
## [1] 0
## [1] -1
Řešení pomocí pevného kroku ve funkci seq() sice neudělá tento neočekávaný výsledek, ale skončí s chybou (což je sice nepříjemné - musíme to nějak vyřešit - ale aspoň víme o problému a program si “nedělá, co chce”, jako v předchozí variantě.)
N <- 0
for (i in seq(0, N-1, by = 1)) {
print(i)
}
Skript by se předčasně ukončil s chybou: Error in seq.default(0, N - 1, by = 1) : wrong sign in ‘by’ argument.
Ideální by tedy bylo mít funkci, která by pro zadaný rozsah hodnot s krokem +1 vyrobila prázdný vektor. Tím by for cykl neproběhl, ale ani neskončil s chybou. Tedy něco ve stylu funkce seq_along(), která pro prázdný vektor vrátí prázdný rozsah indexů:
seq_along(c())
## integer(0)
Takovou funkci nalezneme např. v balíčku library(rPraat) a jmenuje se seqM(), protože se snaží napodobit inteligentní chování matlabovské funkce linspace().
library(rPraat)
N <- 0
for (i in seqM(0, N-1, by = 1)) {
print(i)
}
print("A pokračujeme...")
## [1] "A pokračujeme..."
pocet <- 0
while (pocet < 10) { # opakuj, dokud platí podmínka
print(pocet)
pocet <- pocet + 1
}
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
x <- 0
repeat { # opakuj pořád
x <- x + 1
if (x < 3)
next # skoč na repeat
if (x > 9)
break # vyskoč ven z tohoto cyklu
print(x)
}
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
Pozn. takovýmto strukturám je lépe se vyhýbat, protože se jednoduše může stát chyba v úvaze a cyklus nikdy neskončí. Odborně se tomu říká tzv. zacyklení.
Cykly a další bloky lze navzájem vnořovat. Co bude výsledkem následujících programů?
for (i in 1:3) {
for (j in 1:5) {
cat("i = ", i, ", j = ", j, "\n", sep = "")
}
}
for (i in 1:3) {
for (j in 1:5) {
cat("i = ", i, ", j = ", j, "\n", sep = "")
break
}
}
for (i in 1:3) {
for (j in 1:5) {
cat("i = ", i, ", j = ", j, "\n", sep = "")
}
break
}
for (i in 1:8) {
cat(i, ". ", sep = "")
for (j in 1:8) {
cat(j, " ", sep = "")
}
cat("\n")
}
## 1. 1 2 3 4 5 6 7 8
## 2. 1 2 3 4 5 6 7 8
## 3. 1 2 3 4 5 6 7 8
## 4. 1 2 3 4 5 6 7 8
## 5. 1 2 3 4 5 6 7 8
## 6. 1 2 3 4 5 6 7 8
## 7. 1 2 3 4 5 6 7 8
## 8. 1 2 3 4 5 6 7 8
V R na ně díky dobře vymyšlené funkci seq() skoro nenarazíme.
for (i in seq(0, 0.3, by = 0.1)) {
cat(i, "\n")
}
## 0
## 0.1
## 0.2
## 0.3
Ale to neznamená, že by se R problém netýkal…
i <- 0
while (i <= 0.3) {
cat(i, "\n")
i <- i + 0.1
}
## 0
## 0.1
## 0.2
V jiných jazycích, například v C, je to ale věc, na kterou si musíme dávat velice pozor.
void main() {
float i;
for (i=0; i<=1; i+=0.1) {
printf("%f\n", i);
}
}
Nápověda: ?readline
Funguje jen v interaktivním prostředí (skript R, nikoliv v R Markdown, na webové stránce, prezentacích, Shiny aplikacích apod.)
odpoved <- readline("Jste spokojen s tímto kurzem? ")
print(paste0("Řekl jste: ", odpoved))
# defaultní oddělovač (sep) je mezera.
if (tolower(substr(odpoved, 1, 1)) == "n") {
print("To není možné. Vymýšlíte si!")
} else {
print("V pořádku.")
}
Tip: Zatímco Ctrl+Shift+Enter spustí celý skript a vypisuje i příkazy (tzv. echo), Ctrl+Shift+S spustí celý skript, ale bez echa.
Typ výstupu je character (řetězec).
vstup <- "123"
vstup + 5 # Error in "123" + 5 : non-numeric argument to binary operator
Převod na číslo: as.numeric(“123”)
vstup <- "123"
cislo <- as.numeric(vstup)
cislo + 5
## [1] 128
obvodKruhu <- function(polomer) {
obvod <- 2*pi*polomer
return(obvod) # poslední prováděný příkaz musí tzv. vrátit výslednou hodnotu
# (což může být teoreticky klidně list nebo data.frame plný mnoha hodnot)
}
obvodKruhu(10)
## [1] 62.83185
obvodObdelniku <- function(a, b) { # dva parametry nejsou problém
obvod <- 2*(a+b)
return(obvod)
}
obvodObdelniku(3, 5)
## [1] 16
obvodObdelniku <- function(a = 2, b = 10) { # defaultní hodnoty parametrů, uživatel je pak nemusí vyplňovat
obvod <- 2*(a+b)
return(obvod)
}
obvodObdelniku(b = 8)
## [1] 20
Funkce může vracet více hodnot prostřednictvím vektoru nebo list.
zpracujObdelnik <- function(a, b) {
obvod <- 2*(a+b)
obsah <- a*b
return(c(Obvod = obvod, Obsah = obsah)) # prvky lze dokonce i pojmenovat (to ovšem není nutné)
}
odpoved <- zpracujObdelnik(3, 5)
odpoved["Obvod"] * 3
## Obvod
## 48
Zaokrouhlování může být problém. Prozkoumáme-li nápovědu funkce round, chování nás může překvapit.
?round
round(0.5)
## [1] 0
round(1.5)
## [1] 2
round(2.5)
## [1] 2
round(3.5)
## [1] 4
Vyplatí se vytvořit si vlastní funkci.
zaokrouhli <- function(cislo) {
cele <- trunc(cislo + sign(cislo)*0.5)
return(cele)
}
zaokrouhli(0.5)
## [1] 1
zaokrouhli(1.5)
## [1] 2
zaokrouhli(2.5)
## [1] 3
zaokrouhli(-0.5)
## [1] -1
zaokrouhli(-1.5)
## [1] -2
zaokrouhli(1.4)
## [1] 1
zaokrouhli(-1.4)
## [1] -1
Pozn. trunc odřízne desetinnou část, sign vrátí -1, 0, 1 podle znaménka čísla. Další užitečné funkce: ceiling zaokrouhlí vždy na vyšší číslo, floor na nižší.
ceiling(2.5)
## [1] 3
ceiling(2.1)
## [1] 3
ceiling(-2.9)
## [1] -2
A nešlo by zaokrouhli() implementovat jinak? A co bude rychlejší?
zaokrouhli2 <- function(cislo) {
if (cislo < 0) {
posun <- -0.5
} else {
posun <- 0.5
}
cele <- trunc(cislo + posun)
return(cele)
}
system.time(for (i in 1:10000000) {
zaokrouhli(2.5)
}
)
## user system elapsed
## 5.72 0.03 5.80
system.time(for (i in 1:10000000) {
zaokrouhli2(2.5)
}
)
## user system elapsed
## 6.49 0.00 6.57
© 25. 11. 2015 Tomáš Bořil, borilt@gmail.com