Mariusz Prowaźnik

o programowaniu w Javie, Scali i Clojure.


Co lubię w Clojure cz.2

Kolejną fajną rzeczą w Clojure są funkcje wyższego rzędu map, filter, reduce. (funkcje wyższego rzędu to takie które przyjmują inne funkcje jako argument, lub ich wartością zwracaną jest funkcja).

Zanim podam przykłady użycia tych funkcji, wspomnę, że w Clojure wygodniej niż w Javie tworzy się listy, mapy, zbiory i wektory:

(def some-list1 '(1 2 3 4 5))
(def some-list2 (list 1 2 3 4 5))
(def some-vector1 [1 2 3 4 5])
(def some-vector2 (vec 1 2 3 4 5))
(def some-set1 #{1 2 3 4 5})
(def some-set2 (hash-set 1 2 3 4 5))
(def some-map1 {:a 1 :b 2 :c 3 :d 4})
(def some-map2 (hash-map :a 1 :b 2 :c 3 :d 4))

Funkcja filter

Zwraca sekwencję elementów, pochodzącej z danej kolekcji, które spełniają określony warunek. Przykładowo, chcemy liczby pierwsze z wektora some-vector1. Zdefiniujmy funkcję zwracającą true dla liczb pierwszych, false dla złożonych:

(defn prime? [x]
     (if (> x 1)
       (let [divisors
             (->> x (Math/sqrt) (int) (inc) (range 2))]
         (not (some #(zero? (mod x %)) divisors))
         )
       false))
Mając taką funkcję, przefiltrowanie wektora można zapisać w jednej linijce:
(filter prime? some-vector1) ; rezultat: (2 3 5)

Funkcja map

Ta funkcja przyjmuje dwa argumenty: inną funkcję i kolekcję. Rezultatem jest sekwencja elementów, powstałych na skutek zastosowania przekazanej w argumencie funkcji na elementach kolekcji. Zatem, mając wektor liczb, możemy w jednej linijce podnieść wszystkie do kwadratu:

(map #(* % %) some-vector1) ; rezultat: (1 4 9 16 25)
Programując w Javie często spotykam się z przypadkiem, że mając listę obiektów, potrzebuję utworzyć listę, w której każdy element to określone pole. Na przykład z listy osób stworzyć listę ich imion. W clojure coś takiego robi się bardzo elegancko:
(def peoples [{:name "Jan" :age 20} {:name "Tomasz" :age 15} {:name "Conan" :age 60}])
(map :name peoples) ; rezultat: ("Jan" "Tomasz" "Conan")

Funkcja reduce

Kolejną ciekawą funkcją wyższego rzędu jest reduce. Działa w ten sposób, że stosuje funkcję podaną w argumencie na pierwszych dwóch elementach kolekcji, potem na wyniku i trzecim elemencie itd. Przykład

(reduce + some-vector1) ; rezultat 15

Leniwe sekwencje

Rezultatem funkcji map jest leniwa sekwencja. Oznacza to, że żaden element tej sekwencji nie jest obliczany, dopóki nie zostanie użyty.

Wcześniej napisana funkcja prime? dla większych liczb wykonuje się dosyć długo:

(time (prime? 200560490131)) ; "Elapsed time: 51.440427 msecs"
(time (prime? 2147483647)) ; "Elapsed time: 4.971057 msecs"
(time (prime? 87178291199)) ; "Elapsed time: 84.389674 msecs"

dlatego użyję jej w przykładzie.

(def prime-vec [87178291199 200560490131 2147483647])
(time (def prime-bools (map prime? prime-vec))) ; "Elapsed time: 0.057807 msecs"
(time (nth prime-bools 1)) ; "Elapsed time: 31.690193 msecs"
(time (nth prime-bools 1)) ; "Elapsed time: 0.029071 msecs"

Funkcja memoize

Za pomocą memoize można stworzyć wersję funkcji, która "zapamiętuje" parametry z jakim została wywołana i zwracane wartości, dzięki czemu, gdy zostanie wywołana potem z takimi samymi argumentami, wynik nie musi być liczony od nowa. Przykład:

(def cached-prime? (memoize prime?))
(time (cached-prime? 200560490131)) ;"Elapsed time: 275.652701 msecs"
(time (cached-prime? 200560490131));"Elapsed time: 0.105393 msecs"

Brak komentarzy :

Prześlij komentarz