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
lub
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 if
a. Poprawny kod to:
## [1] "nie ok"
Podobny, ale błędny kod to:
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:
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:
## [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:
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ę
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
Wygeneruj próbę 50-cio elementową \(x=(x_{1},…,x_{50})\) z rozkładu jednostajnego na przedziale \([0,1]\) używając funkcji
runif()
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ętlifor
,while
irepeat
.
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\) .
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\).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}\).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.
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 liczbyA
przez4
. Rok A można określić wcześniej, na następnych zajęciach przerobimy program na funkcję, gdzie A będzie argumentem.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:
## [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ębnieniei
-tej litery ciągu znakówwyraz
. UWAGA: wersjasubstr("wyraz",i,j)
da nam “podciąg” odi
-tej doj
-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"