1 Własne funkcje

Funkcje są to fragmenty kodu programu wywoływane w różnych miejscach z “niewielką” różnicą w argumentach. Pozwalają na uproszczenie programu i zwiększenie jego czytelności. Najprostszy przykład funkcji, która nie ma nawet nazwy:

function(a,b) a+b^2
## function(a,b) a+b^2

to funkcja, która do pierwszego argumentu funkcji dodaje kwadrat drugiego. W nawiasach po słowie kluczowym function umieszcza się argumenty funkcji, po czym w nawiasach {…} znajduje się tak zwane ciało funkcji czyli zbiór instrukcji do wykonania przez funkcję (W przypadku pojedynczej instrukcji nawiasy nie są potrzebne).

W R funkcja jest zwykłym obiektem, takim jak liczba czy napis. Nawet gdy nie ma nazwy można ją wywołać za pomocą operatora () jak niżej:

(function(a,b) a+b^2)(1,2)
## [1] 5

gdzie wywołujemy funkcję z argumentami a=1 oraz b=2.

1.1 Argumenty i wartości

Funkcje mogą przekazywać (zwracać) wartości. Wtedy za wynik funkcji przyjmowana jest wartość wyznaczona w ostatniej linii ciała funkcji. Jeżeli potrzebne jest zwrócenie innych wartości, można skorzystać z polecenia return() z argumentami będącymi wartościami do zwrócenia.

Prościej się korzysta z funkcji, gdy mają one nazwę:

ab2=function(a,b) a+b^2
ab2(1,3)
## [1] 10

Funkcja nie musi mieć argumentów, na przykład funkcja

Sys.time()
## [1] "2024-10-28 18:26:42 CET"

zwraca aktualny czas.

Funkcje mogą mieć dowolną liczbę argumentów, każdy argument musi mieć unikalną nazwę. Do każdego argumentu możemy wskazać wartość domyślną, która będzie stosowana, jeśli dany argument nie zostanie użyty przy wywołaniu funkcji. Na przykład funkcja runif do generowania próby prostej z rozkładu jednostajnego domyślnie przyjmuje przedział \([0,1]\). Równoważne są wywołania:

runif(10)
##  [1] 0.9282663 0.4631596 0.6485014 0.1081897 0.1373470 0.9054586 0.3704333
##  [8] 0.9368558 0.1804013 0.6373562
runif(10,0,1)
##  [1] 0.2082895 0.2231538 0.4379656 0.7116745 0.2086470 0.7890385 0.9104379
##  [8] 0.5834334 0.4073196 0.4986509
runif(10,min=0,max=1)
##  [1] 0.08242925 0.54981644 0.83545288 0.54058702 0.79264699 0.07431787
##  [7] 0.23538431 0.71292155 0.21701540 0.39701688

(otrzymane wektory nie są, na szczęście, identyczne…).

Przy wywołaniu funkcji można przez nazwę wskazać, który argument jest określany. Jeżeli wywołujemy funkcję z wartościami, ale bez podania nazw argumentów, to wartości będą przypisane do kolejnych argumentów, jak w poniższych przykładach:

ab2=function(a=1,b=2) a+b^2
ab2(2)
## [1] 6
ab2(b=1)
## [1] 2
ab2(b=1,2)
## [1] 3

1.2 Zmienne lokalne i globalne

Argumenty przekazywane są do funkcji poprzez wartości, wewnątrz funkcji zmienne są lokalne.

rm(n) #usuwa "n", o ile gdzieś się pojawiła taka zmienna
y=5 #przypisanie wartości zmiennej "y"

power=function(x,n){ # nowa funkcja
  y=x^n
  return(y)
}
power(3,5) #wywołanie funkcji
## [1] 243
y # wartość "y" poza funkcją nie zmieniła się
## [1] 5

W czasie wykonywania funkcji przekazywana jest kopia argumentów, a funkcja nie zmienia wartości zmiennych dostępnych w środowisku pracy. Obiekty ze środowiska pracy mogą zostać wykorzystane przez funkcję:

rm(n) #usuwa "n", o ile gdzieś się pojawiła taka zmienna
power2=function(x){# n nie jest już argumentem
  y=x^n
  return(y)
}
power2(3) # próba wywołania kończy się błędem
n=4 # tworzy globalnego "n"
power2(3) # wywołanie ma teraz sens, choć jakiś fragment kodu może tego "n" podmienić
## [1] 81

Powyższy sposób (korzystanie ze zmiennych globalnych) jest częstym źródłem błędów (nie tylko w R), bezpieczniej jest zadeklarować wszystkie zmienne wykorzystywane przez funkcję jako dane wejściowe.

1.3 Zwracanie więcej niż jednej wartości

Jeżeli zachodzi potrzeba, by funkcja zwracała więcej niż jedna wartość można posłużyć się listą. Poniższa funkcja jako argumenty przyjmuje długość n próby prostej z rozkładu normalnego o średniej sr i odchyleniu standardowym odch. Funkcja generuje wspomnianą próbę, oblicza średnią i odchylenie “empiryczne” wygenerowanej próbki, a następnie zwraca listę - trójkę: wygenerowaną próbę, jej średnią i odchylenie standardowe w postaci listy:

losuj=function(n=50,sr=0,odch=1)
{x=rnorm(n,mean=sr, sd=odch)
  sr_emp=mean(x)
  odch_emp=sd(x)
  return(list(x,sr_emp, odch_emp))
  #list(x,sr_emp, odch_emp) też powinno działać
}
losuj()
## [[1]]
##  [1]  0.939318074  1.533170024 -0.466567987  0.148367354 -0.733629387
##  [6] -0.420184778 -0.300138939 -1.531758781  0.413022892 -0.466841297
## [11]  1.168082171 -1.254703556 -0.917326339 -0.320189005 -1.218400778
## [16]  1.705981157  0.188751775 -1.142091868 -0.786435272 -0.946251690
## [21] -2.520147210  0.771462509 -0.440571035  0.973430363  1.437221735
## [26]  0.152650087  1.448571896 -0.274997158  0.582124568  1.883913358
## [31] -0.080881216 -1.070255324 -0.257141322  1.331485929  1.315161095
## [36]  1.667928874  0.557853024  2.786176360  1.116306700  0.784229380
## [41]  0.784552235 -0.008890368  0.563707109 -0.734664688  1.636496256
## [46] -0.661528929 -1.215003110 -0.003900598 -0.288064493  1.191773103
## 
## [[2]]
## [1] 0.1804235
## 
## [[3]]
## [1] 1.085725

1.4 Funkcja jako wynik

Wynikiem działania funkcji może być inna funkcja:

potega=function(wykladnik)
{
  function(x) x^wykladnik
}
kwadrat=potega(2)
kwadrat(5)
## [1] 25
szescian=potega(3)
szescian(11)
## [1] 1331
pierwiastek=potega(1/2)
pierwiastek(2)
## [1] 1.414214

1.5 Funkcja jako argument

Funkcja może być też argumentem innej funkcji. Poniższa funkcja generuje próbę losową z rozkładu rozklad. Domyślnie przypisany jest rozkład normalny. Trzy kropki w argumencie funkcji służy do pisania “wszystkich pozostałych argumentów” i wykorzystywany jest do przekazywania przez funkcję swoich argumentów do innej funkcji w swoim ciele.

losuj10 = function(rozklad=rnorm, ...)
{
  rozklad(10,...)
}

losuj10()#standardowy rozkład normalny, domyślny dla funkcji
##  [1] -0.6134197  1.0821886  0.2566451 -0.3715695  0.4007833  0.3469545
##  [7]  2.1934789  0.7528285 -0.3389495 -1.2574963
losuj10(runif, -5,5)#... służa do zadania przedziału na którym określony jest rozkład jednostajny (standardowo jest to przedzial [0,1])
##  [1]  1.6610194 -2.6845484  1.1010048 -4.0676396  0.9921379  1.3348370
##  [7]  3.5188631  4.5579076 -4.5474060 -4.0777681

Inny przykład funkcji i wywołania

wykres=function(funkcja=sin,a=0,b=10)
{
  x=seq(a,b,length.out=1001)#sprawdzić, za co odpowiada argument length.out
  y=funkcja(x)
  plot(x,y)
}
wykres() #wywołanie z domyślnymi argumentami

wykres(cos,2,3) #wywołanie z własnymi argumentami

f=function(x) -2*cos(x)+x^2 #"nowa funkcja matematyczna"
wykres(f,0,1)

1.6 Leniwe argumenty, wartościowanie leniwe w R

Wartościowanie leniwe (ang. lazy evaluation, ewaluacja leniwa) – strategia wyznaczania wartości argumentów funkcji tylko wtedy, kiedy są potrzebne (na żądanie).

Zaletami tego podejścia są możliwość obliczenia wartości funkcji nawet wtedy, gdy nie jest możliwe wyznaczenie wartości któregoś z jej argumentów, o ile tylko nie jest on używany, wzrost wydajności dzięki uniknięciu wykonywania niepotrzebnych obliczeń oraz możliwość tworzenia nieskończonych struktur danych. Wadą wartościowania leniwego jest to, że mogą nie wystąpić (być może oczekiwane) skutki uboczne procesu wyznaczania wartości argumentów.

Przeciwieństwem wartościowania leniwego jest wartościowanie zachłanne, stosowane w większości popularnych języków programowania.

(przekopiowane z Wikipedii)

Przykład:

leniwiec=function(x=y)
{
  y=2
  cat(x) #polecenie służące wypisaniu "x" w konsoli
}
y=1
leniwiec(5) #działa jak można by się spodziewać
## 5
leniwiec() #domyślny argument może sprawić problem...
## 2

Jaka jest różnica?

W przypadku pierwszego wywołania leniwiec(5) funkcja ma wypisać w konsoli x. I teraz dopiero funkcja zastanawia się, co to jest ten “x”? Domyślnie to byłby jakiś “y”, ale wywołanie funkcji mówi, że “x” ma być równe 5! Zatem funkcja wypisze liczbę 5.

W drugim przypadku funkcja leniwiec() ma to samo zadanie (wypisanie “x”a), ale nie ma zadanego argumentu w wywołaniu, ale ma argument domyślny. Mam wypisać “x”. Ale co to jest ten “x”?. Domyślnie “x=y”. Ale co to jest ten “y”? Są dwa “igreki”. Jeden globalny, ten y=1, i ten lokalny y=2. Funkcja leniwa wypisze “x=y” dla tego lokalnego y=2. Funkcja nie “zastanawia się”, tzn. nie przypisuje wartości do zmiennych w momencie wywołania funkcji, a dopiero, gdy w ciele funkcji wartość jest jej potrzebna.

2 Zadania

2.1 Zadanie - przestępny

Napisz funkcję przestepny=funkcja(A), sprawdzającą czy rok A jest rokiem przestępnym (jest to ciąg dalszy ćwiczenia z Laboratorium 3).

2.2 Zadanie - silnia

a. Napisz funkcję silnia=function(n) obliczającą \(n!\) (n silnia). Należy zadbać o odpowiednie ograniczenia (klasyczną silnię liczymy dla liczb naturalnych łącznie z zerem). Może być to wersja rekurencyjną lub iteracyjna

b. Oblicz wartość \(n!\) dla wektora \([1,\ldots, 10]\) korzystając ze zbudowanej funkcji silnia oraz apply w odpowiedniej wersji. Wynikiem powinien być wektor silni kolejnych liczb. Wskazówka: wynikiem lapply jest lista, więc aby dostać wektor można np. użyć unlist (sprawdź, jak działa w pomocy) lub inna wersja apply.

c. Napisz funkcję newton=function(n,k) obliczającą wartość symbolu Newtona \({n \choose k}=\frac{n!}{k!(n-k)!}\) wykorzystującą funkcję silnia.

2.3 Zadanie - potega

Napisz funkcję potega=function(A,n) obliczającą \(n\)-tą potęgę macierzy \(A\) za pomocą funkcji for. Należy zadbać o sprawdzenie odpowiednich warunków wewnątrz funkcji (np. czy macierz jest kwadratowa, a potęga naturalna).

2.4 Zadanie - czy_pierwsza

Najprostszy test sprawdzający czy liczba naturalna \(n\) jest liczbą pierwszą polega na sprawdzeniu, czy jest podzielna przez liczby naturalne pomiędzy \(2\) a \(\sqrt{n}\) .

a. Utwórz funkcję czy_pierwsza=function(n) która zwraca:
- komunikat o błędzie, jeżeli \(n\) nie jest liczbą naturalną
- wartość logiczną TRUE gdy \(n\) jest liczba pierwszą
- wartość logiczną FALSE gdy \(n\) jest nie liczba pierwszą

Można np. użyć polecenia %% które daje resztę z dzielenia a przez b.

b. W 1640 roku Fermat przypuszczał, że liczby postaci

\[ F_n=2^{2^n}+1 \]

dla \(n \in \mathbb{N}\) są liczbami pierwszymi (uwaga: we wzorze jest podwójna potęga). Zweryfikuj, korzystając z utworzonej funkcji, czy liczby \(F_1,F_2, F_3, F_4, F_5\) są liczbami pierwszymi.

2.5 Zadanie - test_podzialu

Wektor \(a=(a_1, \ldots, a_n)\) stanowi podział przedziału \([0,1]\), jeżeli spełnione są następujące warunki:
- \(a_1=0\) oraz \(a_n=1\)
- ciąg \((a_n)\) jest ściśle rosnący.

a. Przy użyciu pętli for napisz funkcję test_podzialu=function(a), która zwróci zmienną logiczną
- TRUE, gdy ciąg \(a\) stanowi podział przedziału \([0,1]\)
- FALSE w przeciwnym przypadku.

Przetestuj funkcję np. na ciągach:

\(a=\left(0,\frac{1}{n}, \frac{2}{n}, \ldots, \frac{n-1}{n},1\right)\) dla \(n=100\),

\(a=\left( 0, \frac{2}{3}, \frac{1}{3},1\right)\).

b. Wykonaj podpunkt a. nie korzystając z pętli (można np. użyć polecenia all). Drugą wersję można nazwać np. test_podzialu2=function(a)

c. Korzystając z pętli while napisz funkcję indeksik=function(a,x), która zwraca:
- indeks \(i\), taki że \(a_i \le x <a_{i+1}\), jeżeli taki indeks istnieje i \(a\) jest podziałem \([0,1]\).
- odpowiedni komunikat w przeciwnym przypadku.

Przetestuj działanie funkcji na wektorach \(a\) wyżej wymienionych oraz dla \(x=0.8\) oraz \(x=2\).

d. Wykonaj podpunkt c. bez użycia pętli.

2.6 Zadanie - zlozenie

Napisz funkcję zlozenie, która będzie zwracała funkcję będącą złożeniem funkcji kwadratowej o zadawanych współczynnikach \(a\), \(b\), \(c\) z zadaną funkcją \(f\). Argumentami zlozenie powinny być współczynniki \(a\), \(b\), \(c\) oraz funkcja \(f\). Można przyjąć wybrane wartości domyślne. Dla wybranych argumentów narysuj wykres funkcji.

Przykład: dla argumentów \(a=1\), \(b=2\), \(c=3\) z zadaną funkcją \(f=\sin\) powinniśmy dostać funkcję \[ 1 \cdot \sin^2 x + 2 \cdot \sin x + 3 \] (uwaga: to ma być funkcja, a nie napis).

Wskazówka: przykład wywołania i wykres

kw.sin=zlozenie(1,2,3,sin)
xx=seq(0,10,by=0.1)
plot(xx,kw.sin(xx), type='l')