"The Grid. A digital frontier. I tried to picture clusters of information as they move through the computer. What did they look like? Ships? Motorcycles? Were the circuits like freeways? I kept dreaming of a world I thought I’d never see. And then, one day, I got in." — Tron: Legacy

2012-12-11

Smalltalk : Seaside z użyciem Pharo, cz.3

(c) Robert Tinney

Mam już schemat jak wyciągnąć komentarze z dowolnego bloga, to teraz czas na część wizualną. Tak jak poprzednio, najpierw z grubsza opiszę poszczególne kroki. Film z podsumowaniem dam na końcu. Jako, że film byłby dość długi gdyby pokazywać tworzenie kodu to pokażę w nim tylko jak wygląda struktura pakietu i kod. Z poprzedniej części mam już w zasadzie napisaną podstawę pojedynczej funkcji, która odczyta komentarze z podanego adresu URL i zwróci tablicę komentarzy. Najpierw jednak opiszę jak utworzyć komponent w Seaside.
Otwieram System Browser i zaczynam od utworzenia kategorii (paczki obiektów). Prawy myszki na liście kategorii i klikam "Add category...". Jako, że tworzę pod portal dobreprogramy.pl, utworzę kategorię DobreprogramyAPI.

Tworzenie komponentu głównego

Ten komponent będzie odpowiadać za zawartość strony głównej. To wszystko o czym będę pisać jest także dostępne dostępne w dokumentacji Seaside.. Kasą główną komponentów jest klasa WAComponent. Po niej odziedziczę funkcjonalność na potrzeby mojej nowej klasy "DPRoot":

WAComponent subclass: #DPRoot
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'DobreprogramyAPI'

Pierwsze co trzeba zrobić, to utworzyć metodę "initialize":

initialize
   super initialize.

Metoda "initialize" jest metodą wywoływaną zaraz po utworzeniu obiektu, m.in. po wywołaniu metody "new". Dobrą praktyką jest wywołać metodę "initialize" obiektu nadrzędnego za pomocą metody "super". Zielona strzałka do góry, która się pokaże obok nazwy metody mówi, że moja metoda nadpisuje metodę z klasy nadrzędnej.
Kolejnym krokiem będzie utworzenie metody, która wyświetli coś na stronie. Na chwilę obecną dam jakiś prosty tekst byle zobaczyć, czy działa. Metoda ta posiada określona nazwę, bo Seaside przeszukując obiekty i wyświetlając zawartość szuka właśnie jej:

renderContentOn: html
   html paragraph: 'Alibaba'.

Jako, że obiekt DPRoot jest obiektem głównym, trzeba zaznaczyć to tworząc metodę, która poinformuje framework Seaside. W części klasowej trzeba utworzyć metodę:

canBeRoot
   ^ true.

Dzięki niej Seaside będzie widzieć obiekt w przeglądarce aplikacji.

Rejestracja komponentu jako aplikacji

W przeglądarce przechodzę pod adres http://localhost:8080/config . Klikam w Add na pasku menu. W pole wpisuję 'dp' i wybieram typ jako Application. OK. W sekcji General na kolejnej stronie jako 'Root class' wybieram DPRoot. Klik w Apply. Teraz przechodząc pod adres localhost:8080/dp powinien się wyświetlić Alibaba... Jest. ;)
To samo można zrobić wywołując w Workspace polecenie:

WAAdmin register: DPRoot asApplicationAt: 'dp'.

2012-12-08

Clojure Easy 2


"The computing scientist’s main challenge is not to get confused by the complexities of his own making."
 --- E. W. Dijkstra

Czas na kolejną część zagadek ze strony 4clojure.com.


Problem 9: Fibonacci Sequence


Zadanie polega na napisaniu funkcji, która zwróci n pierwszych elementów ciągu Fibonacci.
Funkcja w E1 bierze za parametr szukaną liczbę elementów ciągu. Dla pierwszego i drugiego elementu zwracane są wartości bezpośrednio. Dla dalszych obliczane są kolejne elementy na podstawie dwóch pierwszych elementów listy acc. W pętli zwiększany jest licznik c i dodawana do początku listy acc suma dwóch poprzednich elementów. Trzeba pamiętać, że funkcja conj dodaje elementy do list od lewej strony, dlatego aby uzyskać poprawną odpowiedź trzeba acc odwrócić przed zwrotem. Odpowiedź E2 jest z wykorzystaniem wektora.

T1: (= (__ 3) '(1 1 2))
T2: (= (__ 6) '(1 1 2 3 5 8))
T3: (= (__ 8) '(1 1 2 3 5 8 13 21))

E1: (fn fiblist [n]
      (let [a '(1) b '(1 1)]
          (cond (= n 1) a
               (= n 2) b
               :else (loop [c 2 acc b]
                    (if (>= c n)
                        (reverse acc)
                        (recur (inc c)
                               (conj acc (+ (first acc) (second acc)))))))))

E2 : (fn fiblist [n]
        (let [a [1] b [1 1]]
            (cond (= n 1) a
                  (= n 2) b
                  :else (loop [c 2 acc b]
                             (if (>= c n)
                              acc
                             (recur (inc c) 
                                    (conj acc (+ (acc (- c 1)) (acc (- c 2))))))))))


Problem 10: Maximum value


Zadanie polega na napisaniu funkcji, która zwróci parametr o największej wartości.
Haczyk: nie można użyć funkcji max i max-key.

Zanim podam wynik wyjaśnię na przykładzie jak to jest z przekazywaniem argumentów do funkcji:

=> ((fn f [a b & more] more) 1 2 3 4)
(3 4)
=> ((fn f [a & more] more) 1 2 3 4)
(2 3 4)
=> ((fn f [& more] more) 1 2 3 4)
(1 2 3 4)

Przed znakiem & argumenty przypisywane są jak leci 1 do a, 2 do b, itd. Po znaku & reszta argumentów traktowana jest jako lista. W przypadku braku identyfikatorów przed & wszystkie argumenty traktowane są jako lista. Oczywiście a, b, more i args mogą mieć dowolne nazwy.

T1: (= (__ 1 8 3 4) 8)
T2: (= (__ 30 20) 30)
T3: (= (__ 45 67 11) 67)

E1:(fn maxx [& args]
            (loop [e (first args)
                   re (rest args)
                   mx e]
                  (if (empty? re)
                       mx
                      (recur (first re) (rest re) (if (> e mx) e mx)))))

Problem 11: Get the Caps


Zadanie: Znaleźć funkcję, która z podanego łańcucha znaków zwróci łańcuch zawierający tylko wielkie litery. Łańcuchy tekstowe w Clojure to zwykłe String z Javy, więc będą na nich działać funkcje z Javy. Aby dostać listę wielkich liter trzeba przefiltrować tekst za pomocą funkcji filter, którą następnie trzeba złączyć w napis przy użyciu funkcji str i apply.


T1: (= (__ "HeLlO, WoRlD!") "HLOWRD")
T2: (empty? (__ "nothing"))
T3: (= (__ "$#A(*&987Zf") "AZ")

E1: #(apply str (filter (fn [c] (Character/isUpperCase c)) %))

Problem 12: Intro to some


Należy podać wynik działania funkcji some. Funkcja ta bierze za pierwszy parametr funkcję, która zwraca logiczną prawdę lub fałsz , oraz kolekcję, której elementy będą przekazywane do tej funkcji. Jeżeli wynikiem działania funkcji będzie true to zostanie zwrócony pierwszy element dla którego warunek został spełniony.


T1: (= __ (some #{2 7 6} [5 6 7 8]))
T2: (= __ (some #(when (even? %) %) [5 6 7 8]))

E1: 6

Problem 13: Duplicate a sequence


Zadanie polega na napisaniu funkcji, która powtórzy elementy sekwencji dwukrotnie.

T1: (= (__ [1 2 3]) '(1 1 2 2 3 3))
T2: (= (__ [:a :a :b :b]) '(:a :a :a :a :b :b :b :b))
T3: (= (__ [[1 2] [3 4]]) '([1 2] [1 2] [3 4] [3 4]))

E1: #(if (empty? %1) []
         (loop [a (first %1) r (rest %1) ac [a a]]
              (if (empty? r) ac
                  (let [s (first r)]
                       (recur s (rest r) (conj ac s s))))))

E2: #(interleave %1 %1)


Problem 14: Implement range


Zadanie polega na napisaniu odpowiednika funkcji range .

T1: (= (__ 1 4) '(1 2 3))
T2: (= (__ -2 2) '(-2 -1 0 1))
T3: (= (__ 5 8) '(5 6 7))

E1: (fn [s e]
        (loop [c s acc []]
             (if (>= c e) 
              acc
             (recur (inc c) (conj acc c)))))

2012-11-22

Clojure Easy 1

"SQL, Lisp, and Haskell are the only programming languages that I've seen where one spends more time thinking than typing."
- Philip Greenspun


Na początek zagadka: Ilu inżynierów od sprzętu komputerowego potrzeba by wymienić żarówkę?
Odpowiedź na końcu wpisu.

W tym cyklu nie będzie poznawania podstawowych funkcji, a raczej ich zastosowanie w akcji. Przewiną się wszystkie poznane już w poprzedniej części konstrukcje oraz pojawią się bardziej zaawansowane funkcje. Będzie więcej kombinowania. Dodatkowo pojawią się przeszkody w postaci zakazów użycia niektórych funkcji.

Zalecam użycie Eclipse (dowolna edycja, może być C++, bo mało zajmuje) oraz wtyczkę Counterclockwise (do Clojure), którą można zainstalować z poziomu Eclipse poprzez Help->Eclipse Marketplace... Po utworzeniu projektu Clojure należy utworzyć namespace np. core. Potem uruchomić przyciskiem Run. Uruchomi się w ten sposób linia poleceń Clojure, gdzie można testować kod. Jeżeli program wpadnie w nieskończoną pętlę, bądź się przywiesi, to można go zatrzymać przy pomocy ikonki z trybikiem lub Ctrl+I.

Problem 1: Last Element


Zadanie polega na wypisaniu ostatniego elementu kolekcji. Aby zadanie nie było zbyt łatwe nie można użyć funkcji last. Oczywiście nie utrudnia to nam zadania.
T1: (= (__ [1 2 3 4 5]) 5)
T2: (= (__ '(5 4 3)) 3)
T3: (= (__ ["b" "c" "d"]) "d")

E1: #(first (reverse %))

Problem 2: Penultimate Element


Jeszcze trudniejsze zadanie. Tym razem trzeba podać wartość drugiego elementu od końca.

T1: (= (__ (list 1 2 3 4 5)) 4)
T2: (= (__ ["a" "b" "c"]) "b")
T3: (= (__ [[1 2] [3 4]]) [1 2])

E1: #(second (reverse %))
E2: #(last (butlast %))

Problem 3: Nth element


Zadanie polega na podaniu n-tego elementu kolekcji przekazanej do funkcji. Jest jednak haczyk. Nie można użyć funkcji nth.

Funkcja take bierze n elementów z listy. Jednak autor testu przyjął podstawę 0. Stąd funkcja musi wziąć o jeden element więcej. Szukany element będzie na ostatniej pozycji z obciętej listy.

T1: (= (__ '(4 5 6 7) 2) 6)
T2: (= (__ [:a :b :c] 0) :a)
T3: (= (__ [1 2 3 4] 1) 2)
T4: (= (__ '([1 2] [3 4] [5 6]) 2) [5 6])

E1: #(last (take (inc %2) %1))

Problem 4: Count a Sequence


Zadanie polega na wypisaniu ilości elementów listy bez użycia funkcji count
Pierwszy sprytny sposób to nudne zliczanie każdego elementu w pętli : E1.
Drugi sprytny sposób: E2, to utworzenie listy jedynek za pomocą funkcji map, które potem będą zsumowane.

T1: (= (__ '(1 2 3 3 1)) 5)
T2: (= (__ "Hello World") 11)
T3: (= (__ [[1 2] [3 4] [5 6]]) 3)
T4: (= (__ '(13)) 1)
T5: (= (__ '(:a :b :c)) 3)

E1: #(loop [c 0 l %]
        (if (empty? l)
            c
            (recur (inc c) (rest l))))
E2: #(reduce + (map (fn [e] 1) %))

Problem 5: Sum it all up


Zadanie polega na napisaniu funkcji, która zwróci sumę wszystkich elementów.
Dużo pracy tu nie ma, bo wykorzystam poprzednią funkcję, która zamiast zliczać jedynki będzie sumować poszczególne elementy.

T1: (= (__ [1 2 3]) 6)
T2: (= (__ (list 0 -2 5 5)) 8)
T3: (= (__ #{4 2 1}) 7)
T4: (= (__ '(0 0 -1)) -1)
T5: (= (__ '(1 10 3)) 14)

E1: #(loop [sum 0 l %]
         (if (empty? l)
             sum
             (recur (+ sum (first l)) (rest l))))
E2: #(apply + %)
E3: #(reduce + %)

Problem 6: Find the odd numbers



Zadanie polega na znalezieniu wszystkich elementów nieparzystych z podanej listy i zwrócenie ich jako listy. Znając już makro for zadanie będzie dość proste (E1). Zmodyfikuję też poprzednią pętlę i podam jako E2.

T1: (= (__ #{1 2 3 4 5}) '(1 3 5))
T2: (= (__ [4 2 1 6]) '(1))
T3: (= (__ [2 2 4 6]) '())
T4: (= (__ [1 1 1 3]) '(1 1 1 3))

E1: #(for [x % :when (odd? x)] x)
E2: #(loop [wyn [] lst %]
            (if (empty? lst)
                wyn
                (recur (if (odd? (first lst))
                           (conj wyn (first lst))
                           wyn)
                        (rest lst))))

Problem 7: Reverse a Sequence


Zadanie polega na zmianie kolejności elementów kolekcji bez używania funkcji reverse i rseq.

T1: (= (__ [1 2 3 4 5]) [5 4 3 2 1])
T2: (= (__ (sorted-set 5 7 2 7)) '(7 5 2))
T3: (= (__ [[1 2][3 4][5 6]]) [[5 6][3 4][1 2]])

E1: #(loop [wyn [] lst %]
               (if (empty? lst)
                   wyn
                   (recur (conj wyn (last lst))
                          (butlast lst))))


Problem 8: Palindrome detector.


Zadanie polega na napisaniu funkcji, która testuje czy podana sekwencja jest palindromem.
Ciąg jest palidromem, jeżeli pierwsza połowa sekwencji jego elementów jest równa odwróconej drugiej połowie. Jeżeli liczba elementów ciągu jest nieparzysta środkowy znak się ignoruje. Należy więc wziąć liczbę n elementów ciągu , podzielić całkowicie przez dwa (funkcja quot), wziąć n/2 pierwszych elementów , odwrócić listę, wziąć n/2 pierwszych elementów i porównać listy (E1). Drugi sposób to porównywać pierwszy element z ostatnim (przy okazji usuwać te elementy), aż lista nie będzie pusta (E2). Trzeci sposób to wziąć sobie jakiś licznik i porównywać n-ty z k-n element listy, gdzie k to długość listy, a n: <0, k-1>.

T1: (false? (__ '(1 2 3 4 5)))
T2: (true? (__ "racecar"))
T3: (true? (__ [:foo :bar :foo]))
T4: (true? (__ '(1 1 3 3 1 1)))
T5: (false? (__ '(:a :b :c)))

E1: #( let [lst %
        n (quot (count lst) 2)
        pp (take n lst)
        dp (take n (reverse lst))]
       (if (< n 1)
           true
          (reduce (fn [x y] (and x y)) (map (fn [a b] (= a b)) pp dp))))

E2: #( let [lst %
        n (quot (count lst) 2)]
        (loop [f (first lst) l (last lst) tmp lst]
          (if (empty? tmp) true
          (if (not= f l)
               false
              (recur (second tmp) (last (butlast tmp)) (rest (butlast tmp)))))))

E3: #( let [v (vec %)
           k (count v)
           n (quot k 2)]
           (loop [f 0 l (dec k)]
             (if (>= f n)
                 true
                 (if (not= (v f) (v l))
                     false
                     (recur (inc f) (dec l))))))

------------------------------------------------------------------------------------------------------------
Odpowiedź na zagadkę: Żadnego. Problem obejdzie się w sofcie. 

Clojure Elementary 4


"If I had a nickel for every time I've written "for (i = 0; i < N; i++)" in C I'd be a millionaire."

- Mike Vanier



Po części trzeciej, czas na ostatnią, czwartą część łamigłówek o podstawowym stopniu trudności. W kolejnej serii przejdę do stopnia trudności określanego jako Easy.


Taka mała zagadka: Ilu programistów .NET potrzeba, by zabić karalucha?
(odpowiedź na końcu wpisu.).

Problem 27: for the win


Zadanie polega na odgadnięciu wyniku działania makra for.
Makro to bierze za pierwszy argument wektor powiązań (podobnie jak let i opcjonalnie trzy modyfikatory:
- :let [ powiązania dla dodatkowych zmiennych pomocniczych ]
- :when ( test ) - zmienne w iteracji zostaną przekazane po spełnieniu testu
- :while ( test ) - for zakończy działanie po negatywnym wyniku testu

Powiązania (nie dotyczy :let) muszą być sekwencjami np.: (for [x '(3 4) z [1 2]] (...) ). Elementy sekwencji będą łączone na zasadzie każdy z każdym. To jest dla każdego elementu x zostanie przypisany każdy element z i taka postać przekazana jako parametry do bloku wykonania. Blok wykonania zwraca wartość, z której po każdej iteracji będzie budowana sekwencja. Tak, że for służy do budowania sekwencji na podstawie innych sekwencji.

W teście T1 mam for, które będzie się wykonywać dla każdego x z sekwencji zbudowanej przy pomocy funkcji range). W tym przypadku range zwróci kolekcję liczb od 0 do 39 włącznie z krokiem 1. Iterując po każdym elemencie zostaje sprawdzony warunek :when, który zwraca prawdę jeżeli reszta z dzielenia elementu x kolekcji przez 4 (funkcja rem będzie równa 1. Dzięki temu zostanie zbudowana kolekcja elementów, których reszta z dzielenia przez 4 jest 1. Wynik w ostateczności powinien wyglądać tak: (1 5 9 13 17 21 25 29 33 37).

W teście T2 x jest nieskończoną kolekcją zwróconą przez funkcję iterate. Funkcja ta bierze za pierwszy argument funkcję, a za drugi wartość początkową. Funkcja anonimowa zwraca wartość argumentu powiększonego o 4. Stąd x będzie mieć postać: (0 4 8 12 ... ). Modyfikator :let tworzy zmienną pomocniczą z, która będzie mieć wartość elementu x powiększonego o 1. Modyfikator :while sprawdza, czy z jest mniejsze od 40, jeżeli nie, to kończy działanie for

W teście T3 for iteruje po parach liczb utworzonych poprzez utworzenie kolekcji liczb
(0 1 2 3 ... 19) podzielonej na kolekcję dwuelementowych list : ( (0 1) (2 3) (4 5) .... (18 19) ).
Wynikiem jest lista, której elementy to sumy par liczb.
Pary i większe kolekcje liczb można przypisywać do zmiennych lokalnych w rózny sposób, np:

(let [ x 1 y 2 ] (+ x y))
i
(let [ [x y] '(1 2) ] (+ x y))
vs
(let [ x (first '(1 2)) y (second '(1 2)) ] (+ x y))
I zadanie:

T1: (= __ (for [x (range 40)
            :when (= 1 (rem x 4))]
        x))
T2: (= __ (for [x (iterate #(+ 4 %) 0)
            :let [z (inc x)]
            :while (< z 40)]
        z))
T3: (= __ (for [[x y] (partition 2 (range 20))]
        (+ x y)))

E1: '(1 5 9 13 17 21 25 29 33 37).
E2: [1 5 9 13 17 21 25 29 33 37].

Problem 28: Logical falsity and truth


W Clojure w testach logicznych tylko nil i false zwracają logiczny fałsz, wszystko inne ewaluuje się do logicznej prawdy. Funkcja if Służy do wykonywania dwóch rodzajów kodu w zależności od wyniku przekazanego do niej testu. Za pierwszy argument bierze test, który zwraca prawdę lub fałsz. Jeżeli wynikiem testu będzie prawda, zostanie wykonany kod podany w drugim argumencie w przeciwnym wypadku zostanie wykonany kod w trzecim. Jeżeli trzeciego argumentu nie będzie, a test sugeruje na jego wykonanie, to if zwróci nil.

T1: (= __ (if-not false 1 0))
T2: (= __ (if-not nil 1 0))
T3: (= __ (if true 1 0))
T4: (= __ (if [] 1 0))
T5: (= __ (if [0] 1 0))
T6: (= __ (if 0 1 0))
T7: (= __ (if 1 1 0))
  
E1: 1

Problem 29: Map defaults


W przypadku pozyskiwania wartości rezydującej pod kluczem mapy, oprócz sposobu jaki był przedstawiony w problemie nr 26 jest jeszcze jedna metoda na poradzenie sobie z sytuacją w której dany klucz nie istnieje. Można do wyrażenia pobierającego wartość spod klucza dodać wartość domyślną, która zostanie zwrócona, gdy klucz nie istnieje, np.:

(:k {:a 0, :b 1, :c nil} :not_exist)
Zwróci :not_exist.

A co jeżeli chcemy utworzyć mapę i nadać kluczom jakaś domyślną wartość?
Zadanie polega na utworzeniu funkcji, która pobierze sekwencję kluczy, wartość domyślną i utworzy z nich mapę. Mapę w najprostszy sposób tworzy się poprzez (hash-map klucz dane) lub {klucz dane}. Wiadomo, że przez elementy sekwencji można iterować przez użycie map lub for. Funkcja map bierze za parametr funkcję, którą stosuje dla każdego elementu sekwencji. Stąd mając domyślą wartość klucza i sekwencję kluczy można zwrócić sekwencję map. Złączyć to można poleceniem conj i funkcją apply lub reduce.

Rozpiszę główne etapy:
1. map ... : ({:a 0} {:b 0} {:c 0})
2. apply conj ... : {:a 0 :b 0 :c 0}
z czego apply tworzy listę w sposób: (conj {:a 0} {:b 0} {:c 0})
reduce: (conj {:a 0} {:b 0}) -> (conj {:a 0 :b 0} {:c 0}) ...

Zadanie:

(= (__ 0 [:a :b :c]) {:a 0 :b 0 :c 0})
(= (__ "x" [1 2 3]) {1 "x" 2 "x" 3 "x"})
(= (__ [:a :b] [:foo :bar]) {:foo [:a :b] :bar [:a :b]})

E1: #(reduce conj (map (fn [d] {d %1}) %2))
E2: #(apply conj (map (fn [d] {d %1}) %2))
------------------------------------------------------------------------------------------------------------
Odpowiedź na zagadkę: Dwóch. Jeden trzyma karalucha, by nie uciekł, a drugi instaluje na nim Windows.

Clojure Elementary 3

“Don't worry about what anybody else is going to do. The best way to predict the future is to invent it.”

— Alan Kay 
Kolejna część zmagań z zadaniami ze strony 4Clojure.
Do dzieła!: ;)



Problem 17: Sequences : filter.

Zadanie polega na zgadnięciu rezultatu filtrowania listy za pomocą funkcji testującej. Funkcja filter bierze za pierwszy argument funkcję testującą a drugi sekwencję. Zwraca elementy sekwencji, które przejdą test pozytywnie. Tutaj funkcja anonimowa zwraca logiczną prawdę jeżeli przekazany do niej parametr jest większy od 5, a więc filter powinien zwrócić wszystkie elementy sekwencji większe od 5.
T1: (= __ (filter #(> % 5) '(3 4 5 6 7)))
E1: '(6 7)
E2: [6 7]


Problem 18: Local bindings.


Funkcja let pozwala na lokalne przypisanie nazw funkcjom bądź danym. Bardzo ułatwia czytanie kodu. Budowa jest postaci:
(let [zmienna1 funkcja1 zmienna2 funkcja2 .... zmiennaN funkcjaN] (obliczena z wykorzystaniem zmienna1..N))
Zadanie polega na odgadnięciu rezultatu działania funkcji.
T1: (= __ (let [x 5] (+ 2 x)))
T2: (= __ (let [x 3, y 10] (- y x)))
T3: (= __ (let [x 21] (let [y 3] (/ x y))))
E1: 7


Problem 19: Let it Be


Zadanie odwrotne do zadania 18. Zamiast zgadywać wynik, trzeba zgadnąć postać przypisania. Zadanie jest trudne bo trzeba zgadnąć wartości przypisań. Najlepiej zacząć od dołu (T3).
T1: (= 10 (let __ (+ x y)))
T2: (= 4 (let __ (+ y z)))
T3: (= 1 (let __ z))
E1: [z 1 y 3 x 7]

Problem 20: Regular Expressions


Wyrażenia regularne. Tym razem trzeba zgadnąć rezultat działania funkcji re-seq, która za pierwszy parametr bierze postać wyrażenia regularnego, a jako drugi ciąg znaków do przeszukania. Wyrażenie regularne w Clojure ma postać: #" ". re-seq zwraca każde poprawne wystąpienie tekstu spełniającego wyrażenie w postaci sekwencji. Funkcję str już znamy. Funkcja apply natomiast powoduje użycie listy jako argumentów do funkcji str.
Zamiast:
(str "a" "b" "c")
jest:
(apply str '("a" "b" "c"))
I zadanie:
T1: (= __ (apply str (re-seq #"[A-Z]+" "bA1B3Ce ")))
E1: "ABC"

Problem 21: Intro to Reduce



Funkcja reduce bierze dwa lub trzy argumenty. Pierwszym jest funkcja, która może przyjąć dwa argumenty. Jeżeli drugim argumentem jest kolekcja, reduce bierze pierwsze dwa elementy z kolekcji i przekazuje je do funkcji. Jeżeli są jeszcze elementy w kolekcji, to brany jest wynik poprzedniej operacji na elementach, kolejny element z listy i przekazywane do podanej funkcji aż do wyczerpania elementów. Jeżeli zamiast kolekcji podamy jakąś wartość i kolekcję, to pierwszym elementem przekazanym do funkcji będzie ta wartość, a drugim pierwszy element z kolekcji.
Zadnie polega na zgadnięciu postaci funkcji jaka zastosowana według powyższych reguł da podany wynik.
T1: (= 15 (reduce __ [1 2 3 4 5]))
T2: (=  0 (reduce __ []))
T3: (=  6 (reduce __ 1 [2 3]))
E1: +
E2: (fn add
      ([] 0)
      ([a b] (+ a b)))
Uwaga. W odpowiedzi użyłem dziwnie wyglądającej funkcji anonimowej. Wcześniej napisałem, że funkcja reduce potrzebuje za pierwszy argument funkcji, która potrafi przyjąć dwa argumenty. Niestety, test T2 wymaga, by funkcja radziła sobie z brakiem argumentów. Clojure pozwala skonstruować funkcje tak, by w jednej definicji przewidywały odpowiedź na różną ilość argumentów. Te definicje oddziela się nawiasami okrągłymi i mogą być w dowolnej kolejności.

Problem 22: Simple Recursion


Rekurencja. Występuje wtedy, gdy funkcja wywołuje samą siebie. W tym zadaniu funkcja foo bierze liczbę za argument x. Jeżeli x jest większe od 0 (when) wykonuje funkcję conj, która tworzy sekwencję z funkcji foo pomniejszonej o 1 i aktualnego x. Funkcja conj łączy do początku listy, ale wykona się rekurencyjnie dopiero po napotkaniu warunku brzegowego (x > 0 ). Jeżeli x będzie 0 funkcja when zwróci nil. conj z nil i x daje (x). Ostatnim x przed warunkiem jest 1, pierwszym jest 5. Czyli łącząc do początku listy od 1 do 5 mamy listę w kolejności odwrotnej.

T1: (= __ (   (fn foo [x]
                      (when (> x 0)
                            (conj (foo (dec x))
                                  x)))
               5 ))

E1:  '(5 4 3 2 1)
E2: [5 4 3 2 1]


Problem 23: Rearranging Code: ->


Makro -> pozwala wykonywać na danych operacje sekwencyjnie zamiast zagnieżdżać w nawiasy. Co my tutaj mamy?

Funkcja reverse odwraca kolejność elementów w kolekcji, czyli będzie: [6 3 1 4 5 2]. rest zwraca wszystkie elementy oprócz pierwszego, więc: [3 1 4 5 2]. sort porządkuje elementy w kolejności rosnącej: [1 2 3 4 5]. Zadanie polega na zgadnięciu ostatniej funkcji, która po zastosowaniu na ostatnim wyniku zwróci 5.

T1: (= (__ (sort (rest (reverse [2 5 4 1 3 6]))))
       (-> [2 5 4 1 3 6] reverse rest sort __)
       5)

E1: last
E2: #(first (reverse %))

Uwaga, może nie zadziałać na 4Clojure mimo, że odpowiedzi są poprawne.

Problem 24: Recurring Theme



Clojure posiada konstrukcję, która pozwala na zapętlanie bez zapychania stosu - taki odpowiednik for. W językach takich jak C, Java kolejne wywołania funkcji powodują, że na stosie odkładane są kolejne ich parametry. Dodatkowo na stosie odkładane są zmienne lokalne funkcji. Jako, że stos nie jest nieskończony może się dość szybko zapchać, szczególnie, gdy stosujemy pętle rekurencyjne, które polegają na wywołaniach funkcji przez samą siebie.
Jako anegdotę dodam, że twórcom gry MMO : Eve Online udało się osiągnąć limit jaki stawia stos C. Wtedy w obroty wzięli odnogę Pyhona: Stackless, który nie zużywa stosu. Jako, że Python ma problemy ze skalowalnością horyzontalną (winowajca: GIL) ciekawi mnie jak rozwiązali sprawę komunikacji pomiędzy procesami działającymi na wielu rdzeniach.
Wracając do tematu. Konstrukcją Clojure, która nie powoduje przepełnień stosu jest loop - recur .
loop bierze wektor zmiennych tymczasowych podobnie do let, które są aktualizowane przez funkcję recur.
Rozkładając na czynniki pierwsze T1 mamy zainicjalizowaną zmienną x wartością 5 i sekwencję result jako pusty wektor: []. Idąc dalej mamy test x > 0. Jeżeli x będzie większe od 0 zostanie wywołana funkcja recur z nowym x pomniejszonym o jeden, a do pustego result zostanie dodany wynik powiększenia x o 2. Jeżeli warunek x > 0 nie będzie spełniony zostanie zwrócona aktualna wartość result. Czyli zaczynając od 5 do 1 włącznie zostanie zbudowany wektor [7 6 5 4 3].

  

T1: (= __
      (loop [x 5
             result []]
        (if (> x 0)
          (recur (dec x) (conj result (+ 2 x)))
          result)))

E1: [7 6 5 4 3]

Problem 25: Rearranging Code: ->>


Makro ->>. Służy do poprawy czytelności kodu. Jest to rozwinięta wersja ->, gdzie funkcje były jedno-parametrowe. Makro bierze dane z pierwszego argumentu i wstawia je na koniec listy podanej w kolejnym argumencie. Jeżeli są dodatkowe listy, to wynik poprzedniego działania jest wstawiany na koniec kolejnej. Na końcu tak zbudowana lista jest ewaluowana (wykonywana).
W zadaniu mamy wektor [2 5 4 1 3 6], który jest skracany o dwa elementy z przodu funkcją drop. Wynik to : [4 1 3 6] Następnie z wyniku brane są trzy pierwsze elementy : [4 1 3]. Kolejna rzecz, to za pomocą map zwiększenie elementów o 1, dając: [5 2 4].
Teraz trzeba znaleźć taką funkcję, która zrobi jakoś z tych danych 11. Okazuje się, że suma elementów to 11, więc użyję + wraz z funkcją operująca na listach.

T1: (= (__ (map inc (take 3 (drop 2 [2 5 4 1 3 6]))))
       (->> [2 5 4 1 3 6] (drop 2) (take 3) (map inc) (__))
       11)

E1: reduce +
E2: apply +

Problem 26: A nil key


Zadanie polega na znalezieniu funkcji, która pobierze dwa argumenty: klucz i mapę, oraz zwróci prawdę jeżeli klucz istnieje w tej mapie i ma wartość nil. Jest to dość ciekawe zadanie, bo mapa zwraca nil jeżeli klucz nie istnieje. Jak więc dowiedzieć się, że klucz istnieje i ma wartość nil?

Potrzebne będą dwa porównania. Jedno sprawdza, czy klucz istnieje za pomocą funkcji contains?, a drugie, czy wartość przypisana do klucza jest nil. Stąd rozwiązanie E1. Drugie rozwiązanie E2 jest lepsze, bo nie iteruje dwukrotnie po mapie tylko raz. W pętli loop biorę pierwszą parę klucz-dane z mapy i sprawdzam, czy nie dostaję nil. Dostanę nil jeżeli mapa będzie pusta. Jeżeli jest pusta zwracam falsz. Jeżeli nie sprawdzam, czy pierwszy element pary to mój klucz i przy okazji sprawdzam czy drugi to nil. Jeżeli to nasz klucz i ma wartość nil zwracam true. W przeciwnym wypadku biorę kolejny element z mapy oraz jej resztę i przekazuję jako nowe wartości do loop za pomocą recur.
Update:

Przykład E3 wykorzystuje możliwość przypisania do zmiennej lokalnej wartości domyślnej jeżeli klucz w mapie nie istnieje. Trzeba jednak uważać, by wartość domyślna nie mogła wystąpić w danych, bo dostaniemy niepoprawny wynik.

T1: (true?  (__ :a {:a nil :b 2}))
T2: (false? (__ :b {:a nil :b 2}))
T3: (false? (__ :c {:a nil :b 2}))

E1: #(and (contains? %2 %1) (nil? (%2 %1)))
E2: #(let [klucz %1 mapa %2]
        (loop [p (first mapa) r (rest mapa)]
                 (if (nil? p)
                     false
                     (if (and (= (first p) klucz) (nil? (second p)))
                             true
                             (recur (first r) (rest r))))))
E3: #(let [dane (%1 %2 :not_exist)]
        (if (nil? dane)
        true
        false))