1 Instrukcje warunkowe if... else...

W języku R, podobnie jak w większości języków programowania, mamy możliwość korzystania z instrukcji warunkowych. Umożliwia ona warunkowe wykonanie kodu w zależności od prawdziwości pewnego warunku logicznego. Jej składnia jest następująca

if(warunek logiczny){
  blok instrukcji 1
}

lub

if(warunek logiczny){
  blok instrukcji 1
} else{
  blok instrukcji 2
}

lub gdy potrzebujemy więcej warunków

if(warunek logiczny 1){
  blok instrukcji 1
} else if(warunek logiczny 2){
  blok instrukcji 2
} else if(warunek logiczny 3){
  blok instrukcji 3
} else{
  blok instrukcji 4
}

Na przykład

a <- 200
b <- 33
if (b > a) {
  print("b jest większe od a")
} else if (a == b) {
  print("a i b są równe")
} else {
  print("a jest większe od b")
} 
## [1] "a jest większe od b"

Uwaga: instrukcja else musi być w tej samej linijce co klamra } zamykająca blok instrukcji od odpowiedniego ifa. Poprawny kod to:

if(3>5){
  print("ok")
} else{
  print("nie ok")
}
## [1] "nie ok"

Podobny, ale błędny kod to:

if(3>5){
  print("ok")
} #else jest w nowej linijce ! TO ŹLE!!! 
else{
  print("nie ok")
}

2 Pętle

Omówimy teraz instrukcje warunkowe while, for, repeat.

2.1 W R należy unikać pętli, bo…

Uwaga: W R należy unikać pętli, ponieważ są one ogólnie nieefektywne, ze względu na długie czasy obliczeń. W większości przypadków jest możliwa wektoryzacja obliczeń, aby uniknąć pętli. Można użyć też funkcji outer, apply, lapply, tapply i mapply aby wykonywać pętle bardziej efektywnie.

Wynika to z faktu, że operacje wektorowe są zoptymalizowane na poziomie wewnętrznym i mogą korzystać z niskopoziomowych bibliotek matematycznych napisanych w językach takich jak C, co pozwala na bardziej efektywne zarządzanie pamięcią i procesorem.

W poniższym kodzie porównamy wydajności operacji wykonywanych za pomocą pętli oraz operacji wektorowych w języku R, wykorzystując bibliotekę microbenchmark do pomiaru czasu wykonywania oraz ggplot2 do wizualizacji wyników.

Uwaga: biblioteki microbenchmark i ggplot2 zostaną szerzej omówione na kolejnych laboratoriach. Poniższy kod można uruchomić na swoim komputerze jako skrypt, gdyż sam R Markdown użyty do wygenerowania tego laboratorium spowalnia (przez co też fałszuje) obliczenia.

library(microbenchmark)
library(ggplot2)
 
#instrukcje początkowe
x=rnorm(1000) # losujemy 1000 liczb ze standardowego rozkładu normalnego
y1=numeric(1000) #alokacja pamięci na 1000 liczb
y2=numeric(1000) #j.w.
#test właściwy
pomiary1=microbenchmark( #funkcja microbenchmark 100 razy powtarza każdą testowaną instrukcję
petla = {for(i in 1:1000) y1[i]=sin(x[i])},  #obliczenie wartości f. sinus 1000x za pomocą pętli
wektorowe = {y2=sin(x)} #obliczenie f. sinus na wektorze o długości 1000
)
 
autoplot(pomiary1) #narysowanie wykresu

W pętli powyżej sinus jest obliczany dla każdej wartości wektora x i jest zapisywany w wektorze y1. Natomiast, w kolejnej linii, sinus jest obliczany dla całego wektora x naraz.

2.2 Pętle - ogólne informacje

Pętla repeat jest zawsze wykonywana co najmniej raz. Powinna ona zawierać warunek przerwania za pomocą polecenia break. Polecenie break może być również użyte do wyjścia z pętli for oraz while. Polecenie next pozwala na natychmiastowe przejście do następnej iteracji pętli for, while lub repeat. Blok instrukcji jest ograniczany nawiasami klamrowymi (niektóre języki używają poleceń begin i end). Warunek logiczny ograniczony jest nawiasami okrągłymi.

2.3 Pętla for

Ogólny schemat pętli for to:

for (iterator in kolekcja) {
  blok instrukcji
}

Prosty przykład pętli for:

x=NULL #inicjalizacja 
for(i in 1:10) #pętla będzie wykonywana dla i od 1 do 10
  {
  x[i]=i #
} #ciąg instrukcji kończymy nawiasem klamrowym
x #wypisanie wektora x
##  [1]  1  2  3  4  5  6  7  8  9 10

Po wykonaniu pętli w oknie Enviroment pojawia się zmienna o nazwie i o wartości 10L - litera L oznacza zmienną typu interger - całkowitą.

Inny przykład wykorzystania pętli for

s=0
x=rnorm(10000) #generuje próbę 10.000 elementową ze standardowego rozkładu normalnego
for(i in 1:10000) #wykonanie pętli dla każdego elementu x od 1 do 10.000
{
  if(x[i]<10)
  {
    s=s+x[i]
  }   else
  {
    s=s+2*x[i]
  }
}
s
## [1] -51.79809

Powyższą pętlę można zastąpić wydajniejszym poleceniem:

sum(x[x<10])+2*sum(x[x>=10])
## [1] -51.79809

W przypadku korzystania z konsoli rozpoczęcie bloku instrukcji poprzez wpisanie { spowoduje zmianę znaku zachęty z > na +. Aby wymusić wyjście R z bloku instrukcji, należy nacisnąć escape(można też dokończyć wprowadzanie instrukcji).

Inne przykłady

wekt=c(1,5,8)
for(i in wekt) #pętla będzie wykonywana dla i z zadanego wektora `wekt`
  {
  print(i^2)
} #ciąg instrukcji kończymy nawiasem klamrowym
## [1] 1
## [1] 25
## [1] 64
wekt=seq(1,2,by=0.2)
for(i in wekt) #pętla będzie wykonywana dla i z zadanego wektora `wekt`
  #- nie muszą być to liczby całkowite
  {
  print(i^2)
} #ciąg instrukcji kończymy nawiasem klamrowym
## [1] 1
## [1] 1.44
## [1] 1.96
## [1] 2.56
## [1] 3.24
## [1] 4

2.3.1 Zadanie - pytanie

Wyjaśnić powód różnego działania pętli:

n=10
for(i in 1:n-4) #pętla będzie wykonywana dla i ... jakich?
  {
  print(i)
} #ciąg instrukcji kończymy nawiasem klamrowym
## [1] -3
## [1] -2
## [1] -1
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6

oraz

n=10
for(i in 1:(n-4)) #pętla będzie wykonywana dla i ... jakich?
  {
  print(i)
} #ciąg instrukcji kończymy nawiasem klamrowym
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6

2.4 Pętla while

Pętla while stanowi trochę bardziej elastyczne narzędzie, gdy liczba powtórzeń pętli nie jest ustalona. Ma następujący szablon:

while (warunek logiczny) {
  blok instrukcji
}

Przykład:

x=NULL #inicjalizacja
i=0 #inicjalizacja
while(i<10) # warunek stopu: blok instrukcji wykonywany jest tak długo, dopóki i <10
{ 
i=i+1 #bez tej instrukcji pętla while byłaby nieskończona
x[i]=i #instrukcja wewnątrz pętli
} #koniec pętli while
x #wypisanie wyniku
##  [1]  1  2  3  4  5  6  7  8  9 10

2.5 Pętla repeat

W przypadku pętli while (patrz powyższy przykład) konieczne jest zainicjalizowanie zmiennej i przed rozpoczęciem pętli. Można tego uniknąć używając pętli repeat, która ma następujący składnię

repeat {
  blok instrukcji
}

Zauważmy, że powyższa pętla będzie działać tak długo aż…. powiemy stop wykorzystując break. Przykład:

x=NULL
i=1
repeat{
  x[i]=i
  if (i>=10){
    break #wyjście z pętli repeat gdy `i` osiągnie lub przekroczy wartość  10
  } #koniec if
  i=i+1 #właściwa instrukcja
} #koniec pętli repeat
x
##  [1]  1  2  3  4  5  6  7  8  9 10

3 Zadania - pętle

3.1 Zadanie pętle

  1. Wygeneruj próbę 50-cio elementową \(x=(x_{1},…,x_{50})\) z rozkładu jednostajnego na przedziale \([0,1]\) używając funkcji runif()

  2. Utwórz wektor \(y=(y_1,\dots, y_{50})\), taki że \(y_i=0,\) gdy \(x_i<0.5\) wygenerowane w podpunkcie a. oraz \(y_i=1\) w przeciwnym przypadku za pomocą instrukcji if oraz, kolejno, pętli for, while i repeat.

3.2 Zadanie pętle

Rozważmy ciąg \((U_n)_{n \ge 1}\) zdefiniowany jako \(U_1=10\) oraz \(U_{n+1}=\sqrt{U_n}\) dla \(n \ge 1\) .

  1. Napisz pętlę for, która oblicza pierwsze \(k\) wyrazów ciągu \((U_n)_{n \ge 1}\) - pętla powinna zwrócić wektor o rozmiarze \(k\).

  2. Napisz pętlę while, która oblicza pierwsze \(n\) wyrazów ciągu, gdzie liczba \(n \ge 1\) jest najmniejszą liczbą naturalną o tej własności, że \(|U_{n+1}-U_{n}| <\varepsilon\) dla ustalonego z góry \(\varepsilon >0\). Podaj wartość \(n\) odpowiadającą \(\varepsilon = 10^{-4}\).

  3. Powtórz powyższe dwa podpunkty używając pętli repeat.

Wskazówka: podpunkt a dla \(k=10\) powinien dać ciąg

##  [1] 10.000000  3.162278  1.778279  1.333521  1.154782  1.074608  1.036633
##  [8]  1.018152  1.009035  1.004507

Wskazówka: podpunkt b powinien dać ciąg

##  [1] 10.000000  3.162278  1.778279  1.333521  1.154782  1.074608  1.036633
##  [8]  1.018152  1.009035  1.004507  1.002251  1.001125  1.000562  1.000281
## [15]  1.000141  1.000070

3.3 Zadanie pętle

Rok przestępny: jeżeli rok A nie jest podzielny przez 4, to A nie jest rokiem przestępnym. Jeżeli A jest podzielne przez 4, to A jest rokiem przestępnym, chyba że A jest podzielne przez 100, ale nie przez 400. Przykłady:
- rok 1901 nie jest rokiem przestępnym, bo nie jest podzielny przez 4
- rok 2004 jest rokiem przestępnym, ponieważ jest podzielny przez 4, a nie przez 100
- rok 2100 nie jest rokiem przestępnym, ponieważ dzieli się przez 4, przez 100, ale nie przez 400
- rok 2000 jest rokiem przestępnym, bo jest podzielny przez 4, przez 100 i przez 400.

  1. Napisz program, który określa, czy rok A jest rokiem przestępnym, czy nie. Możesz użyć np. polecenia A%%4 służącego do obliczania reszty z dzielenia liczby A przez 4. Rok A można określić wcześniej, na następnych zajęciach przerobimy program na funkcję, gdzie A będzie argumentem.

  2. Przetestować program na powyższych przykładach

Uwaga: program powinien wypisać odpowiedź pełnym zdaniem, np. “Rok A jest/nie jest rokiem przestępnym”. Można wykorzystać polecenie paste służące do łączenia tekstu ze zmiennymi, jak w przykładzie:

a=runif(1)
paste('Liczba',a,'jest liczbą wygenerowaną ze standardowego rozkładu normalnego')
## [1] "Liczba 0.189265363849699 jest liczbą wygenerowaną ze standardowego rozkładu normalnego"

Wskazówka: wynik dla roku \(2100\) powinien przypominać

## [1] "rok  2100  nie jest przestepny"

3.4 Zadanie pętle

Używając zagnieżdżonych pętli for, oblicz pierwszych N rzędów trójkąta Pascala używając wzoru rekurencyjnego \({n+1 \choose k} = {n \choose k-1} + {n\choose k}\) dla \(k=1,\dots,n\). Wyniki zapisujemy w macierzy kwadratowej o rozmiarze N.

Wskazówka: dwa pierwsze wiersze oraz skrajne jedynki w każdym wierszu można potraktować nieco inaczej. Wynik nie musi mieć klasycznej postaci trójkąta równoramiennego.

Wskazówka: przykładowy wynik działania kodu (nie musi być identyczny format)

##       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
##  [1,]    1    0    0    0    0    0    0    0    0     0
##  [2,]    1    1    0    0    0    0    0    0    0     0
##  [3,]    1    2    1    0    0    0    0    0    0     0
##  [4,]    1    3    3    1    0    0    0    0    0     0
##  [5,]    1    4    6    4    1    0    0    0    0     0
##  [6,]    1    5   10   10    5    1    0    0    0     0
##  [7,]    1    6   15   20   15    6    1    0    0     0
##  [8,]    1    7   21   35   35   21    7    1    0     0
##  [9,]    1    8   28   56   70   56   28    8    1     0
## [10,]    1    9   36   84  126  126   84   36    9     1

3.5 Zadanie pętle

Palindrom to słowo, które można czytać zarówno od lewej do prawej, jak i od prawej do lewej strony. Przykład: słowo RADAR. Nie mylić z anagramem.

Napisz kod, który określa, czy dany ciąg znaków jest palindromem.

Wskazówka: Można skorzystać z następujących funkcji:

  • substr służy do wyodrębniania części łańcucha znaków, substr("wyraz",i,i) pozwala na wyodrębnienie i-tej litery ciągu znaków wyraz. UWAGA: wersja substr("wyraz",i,j) da nam “podciąg” od i-tej do j-tej litery.

  • nchar podaje rozmiar łańcucha znaków.

  • ceiling(n/2) - wystarczy zbadać “połowę” wyrazu, a funkcja ta przyda się dla wyrazów o nieparzystej liczbie znaków

Dla słowa RADAR program powinien zwrócić “RADAR jest palindromem” (możemy użyć polecenia paste pozwalającego na łączenie zmiennych znakowych). Przetestować program na różnych słowach (np. kajak, ciasto, owocowo, lato…)

Wskazówka: wynik dla wyrazu pizza oraz RADAR

## [1] "pizza nie jest palindromem"
## [1] "RADAR jest palindromem"