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
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:
## [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ę:
## [1] 10
Funkcja nie musi mieć argumentów, na przykład funkcja
## [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:
## [1] 0.9282663 0.4631596 0.6485014 0.1081897 0.1373470 0.9054586 0.3704333
## [8] 0.9368558 0.1804013 0.6373562
## [1] 0.2082895 0.2231538 0.4379656 0.7116745 0.2086470 0.7890385 0.9104379
## [8] 0.5834334 0.4073196 0.4986509
## [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:
## [1] 6
## [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
## [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)
}
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:
## [1] 25
## [1] 1331
## [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
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
## 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