Funkcje pierwszej klasy i funkcje wyższego rzędu¶
Prezentacja: http://www.cs.put.poznan.pl/ksiek/fp/functions/
Funkcje vs Metody w Scali¶
Metoda to część klasy, opisana sygnaturą, zawierająca kod. Funkcja to kompletny obiekt.
def method(arg: Int) = arg + 1
method: (arg: Int)Int
val function = (arg: Int) => arg + 1
function: Int => Int = <function1>
Możemy używać metod jak funkcji i vice versa (konwersja wykoanana przez interpreter).
Składnia¶
Funkcje:
val f = (x: Int) => x + 1
val g = (x: Int, y: Int) => x + y
val h = () => x
val i = () = { System.out.println("Hello World!") }
Typy:
Int => Int
(Int, Int) => Int
() => ()
Funkcje wyższego rzędu i pierwszej klasy¶
Funkcja pierwszej klasy to funkcja która może być traktowana jak wartość: przypisywana do zmiennej, przekazywana przez argumenty.
Funkcja wyższego rzędu to funkcja która przyjmuje funkcję jako argument, lub zwraca funkcję.
val fahrToCels = (temp: Double) => (temp - 32) * 5/9 // function
def celsToFahr(temp: Double) = 32 + temp * 9/5 // method
fahrToCels(100)
celsToFahr(30)
val celsOfFahr = fahrToCels
val fahrOfCels = celsToFahr _ // because: celsToFahr <==> celsToFahr();
celsOfFahr(100)
fahrOfCels(30)
def convert(f: Double => Double, temp: Double) = f(temp)
convert(fahrToCels, 100)
convert(celsToFahr, 30)
Zwracanie funkcji:
val f = (x:Int) => (y: Int) => x + y
val g = (x:Int) => { val z = x + 1; (y: Int) => z + y }
Kompozycja funkcji¶
val f = (x:Int) => x * x
val g = (x:Int) => x + 1
f(g(1)) // (x + 1) * (x + 1) = 4
(f compose g)(1) // (x + 1) * (x + 1) = 4
g(f(1)) // (x * x) + 1 = 2
(f andThen g)(1) // (x * x) + 1 = 2
Funkcje anonimowe¶
(x: Int) => x + 1
Funkcja fold¶
Standardowe rekurencyjne przejście po liście:
def recursiveSum(l: List[Int], acc: Int = 0): Int =
l match {
case Nil => acc
case head::tail => recursiveSum(tail, acc + head)
}
recursiveSum(List(1,1,1,1))
Szablon:
def recursiveOp[T](op: (T, T) => T, l: List[T], acc: T): T =
l match {
case Nil => acc
case head::tail => recursiveOp(op, tail, op(acc, head))
}
recursiveOp((acc:Int, elem:Int) => acc + elem, List(1,1,1,1), 0)
Funkcja fold:
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1
val l = List(1, 1, 1 ,1 ,1)
l.fold(0)((acc: Int, elem: Int) => acc + elem)
val ll = List(List(1, 1, 1), List(2, 2, 2))
ll.fold(List[Int]())((acc: List[Int], elem: List[Int]) => acc ::: elem)
Optymalizacja do wykonania równoległego. Ograniczenie wg typów:
val l = List(1, 1, 1 ,1 ,1)
l.fold(List[Int]())((acc: List[Int], elem: Int) => elem :: acc)
<console>:3: error: type mismatch;
Wykonanie sekwencyjne z wyróżnieniem kierunku wywoływania:
def foldLeft[B](z: B)(op: (B, A) => B): B
def foldRight[B](z: B)(f: (A, B) => B): B
val l = List(1, 2, 3 ,4 ,5)
l.foldLeft(0)((acc: Int, elem: Int) => {println(acc+"+"+elem); acc + elem})
l.foldRight(0)((elem: Int, acc: Int) => {println(acc+"+"+elem); acc + elem})
l.foldLeft(List[Int]())((acc: List[Int], elem: Int) => elem :: acc)
l.foldRight(List[Int]())((elem: Int, acc: List[Int]) => elem :: acc)
Redukcja bez wartości początkowej. (Ograniczenie typów.)
val l = List(1, 2, 3 ,4 ,5)
l.reduce((acc: Int, elem: Int) => acc + elem)
l.reduceLeft((acc: Int, elem: Int) => {acc + elem})
l.reduceRight((elem: Int, acc: Int) => {acc + elem})
Wyniki pośrednie:
val l = List(1, 2, 3 ,4 ,5)
l.scan(0)((acc: Int, elem: Int) => acc + elem)
l.scanLeft(0)((acc: Int, elem: Int) => {acc + elem})
l.scanRight(0)((elem: Int, acc: Int) => {acc + elem})
l.scanLeft(List[Int]())((acc: List[Int], elem: Int) => elem :: acc)
l.scanRight(List[Int]())((elem: Int, acc: List[Int]) => elem :: acc)
Mapowanie wartości:
val l = List(1, 2, 3 ,4 ,5)
l.map((x: Int) => x + 1)
l.map((x: Int) => x.toString())
Mapowanie na n wartości
val l = List(1, 2, 3, 4, 5)
l.flatMap((x: Int) => if (x==0) List(x) else List(x, -x))
Filtrowanie wartości:
val l = List(1, 2, 3, 4, 5)
l.filter((x:Int) => x % 2 == 0)
l.filterNot((x:Int) => x % 2 == 0)
Podział listy:
val l = List(1, 2, 3, 4, 5)
l.parition((x:Int) => x % 2 == 0)
Ćwiczenia¶
#. Problem: Zaimplementuj funkcję cenzury. Funkcja przyjmuje listę słów
zabronionych i listę słów stanowiącą jakąś treść. Każde słowo w treści, które
znajduje się na liście słów zabronionych jest zastąpione napisem
[OCENZURWANO]
w wynikowej liście słów.
- Problem: Zaimplementuj funkcję łączącą dwie listy.
- Problem: Przygotuj własną implementacji funkcji
reduce
,map
,filter
,scan
,partition
używając funkcjifoldLeft
. - Problem: Napisz program wyszukujący w podanym katalogu wszystkich plików które mają powtarzającą się treść. Porównanie pliku wykonaj w dwóch krokach: do wstepnego porównania przygotuj dla każdego pliku skrót (hash); drugi krok porównania to dokładne porównanie treści.
- Problem: Przygotuj wielowątkową implementację funkcji sortującej w modelu MapReduce (np. Bucket sort).
Zadanie [Scala2]¶
Zaimplementuj w Scali program znajdujący najbardziej punktowane możliwe do ułożenia legalne słowo w Scrabble.
Dany jest alfabet znaków A. Dany jest wektor P(c) który określa wartość punktową każdego znaku c w alfabecie. Dany jest też wektor C(c) która dla każdego znaku w alfabecie określa jego liczność. Słowo to dowolna sekwencja znaków. Mając dane słowo w jego punktacja P(w) to suma P(c) dla wszystkich c zawartych w w. Dany jest słownik D zawierający legalne słowa.
Gracz posiada multizbiór R zawierający znaki z A. Znak c występuje w R nie więcej niż C(c) razy.
Napisz funkcję losującą R. Napisz funkcję znajdującą w R takie legalne słowo w którego P(w) jest największe spośród legalnych słów możliwych do utworzenia z R.
Standardowe punkty znaków (i liczności):
0 pt: blank (2)
1 pt: E (12), A (9), I (9), O (8), N (6), R (6), T (6), L (4), S (4), U (4)
2 pt: D (4), G (3)
3 pt: B (2), C (2), M (2), P (2)
4 pt: F (2), H (2), V (2), W (2), Y (2)
5 pt: K (1)
8 pt: J (1), X (1)
10 pt: Q (1), Z (1)
Słowniki: np. /usr/share/dict/
Luźne uwagi: |w| ≤ |R|, sugeruje tylko małe/wielkie litery, można wczytać słownik z pliku
Zadanie [Scala2] (deprecated)¶
Zaimplementuj w Scali system parowania uczestników turnieju wg systemu szwajcarskiego.
Turniej trwa r rund i bierze w nim udział p uczestników (p > r) przy czym każdy uczestnik g pochodzi z kraju N(g). W pierwszej rundzie turnieju uczestnicy są parowani losowo, przy czym uczestnicy z tego samego kraju nie są parowani ze sobą nawzajem. W każdej kolejnej rundzie parowanie następuje wg następujących zasad:
- Dwaj uczestnicy nie spotykają się więcej niż raz.
- Uczestnicy spotykają się w kolejnej rundzie z uczestnikami o podobnej sumie punktów. T.j. jeśli posortować uczesntików wg sumy uzyskanych dotychczas punktów uczestnicy o pozycjach 1 i 2, 3 i 4, 5 i 6, etc. spotykają się w następnej rundzie (z uwzględnieniem wyjątku z pt 1.)
- W miarę możliwości uczestnicy nie spotykają się z innymi uczestnikami z tego samego kraju.
Punktowanie spotkań.
- Zwycięstwo w spotkaniu = 3pt, remis 1pt, przegrana 0pt.
- Jeśli licza uczestników jest nieparzysta, uczestnik który w danej rundzie nie ma przeciwnika dostaje punkty jak za zwycięsto (tzw. bye).
Symulowanie wyników spotkań wg schematu:
val random = new scala.util.Random
val winner = (g1: Any, g2: Any) => {
random.nextInt(3) match {
case 0 => g1
case 1 => g2
case 2 => None
}
}