|
Collections play a central role in clojure
The collections share a common interface.
In clojure
, collections are called sequences.
Usually, sequences are lazy.
Here are a couple of functions available on sequences and here is the full interface for sequences
count
(count (zipmap (range 100) (range 200)))
seq
is useful when we need to test whether a sequence is empty
[(seq [1 2 3])
(seq [])]
We can take a few element - even from an infinite sequence:
(take 10 (range))
We can take elements while a predicate holds:
(take-while (partial > 10) (range))
We can also take the last elements of a list - but don't try that on an infinite list!
(take-last 10 (range 100))
Play also with drop
drop-last
and drop-while
...
map
(map inc [1 2 3])
filter
(filter even? (range 10))
remove
(remove even? (range 10))
reduce
(reduce (fn [res x] (* res x)) 1 (range 1 11))
Compose a string from the letters whose ascii values are odd numbers
We are going to implement this in several ways, improving our code style at each stage:
The worst way:
(reduce (fn [res i]
(if (odd? i) (str res (char i)) res))
"" (range 97 123))
Why is it bad?
Giving each stage a name:
(let [lst (range 97 123)
f-lst (filter odd? lst)
m-lst (map char f-lst)]
(reduce str "" m-lst))
Using the as->
threading macro
(identity (as-> (range 97 123) $
(filter odd? $)
(map char $)
(reduce str "" $)))
Using the ->>
threading macro:
(->> (range 97 123)
(filter odd?)
(map char)
(reduce str ""))
Write by yourslef the following funtions of clojure.core
Write a clojure
version of spark
's function reduceByKey
: receives a list of (K, V)
pairs and a function f
and returns a list of (K, V)
pairs where the values for each key are aggregated using the given reduce function f
(defn reduce-by-key [f lst])
(and
(= (reduce-by-key '((a 1)) +) '((a 1)))
(= (reduce-by-key '((a 1) (a 3)) +) '((a 4)))
(= (reduce-by-key '((a 1) (a 3) (b 1) (a 5) (b 6)) +) '((a 9) (b 7))))
Function that receives function[s] and return a function
apply
(apply + (range 10))
comp
((comp list inc) 9)
You can call apply
on comp
. For instance:
((apply comp [inc inc inc]) 3)
partial
(def match-aa (partial re-matches #"aa"))
[(match-aa "aaaa")
(match-aa "aa")]
memoize
First, a non-memoized function that print its arguments
When called twice with the same argument, it prints twice
(defn foo[x] (println "foo called with: " x))
(symbol (with-out-str
(foo 1)
(foo 1)))
With memoization, the function is called only once, the other calls are cached:
(def foo-memo (memoize foo))
(with-out-str
(foo-memo 1)
(foo-memo 1))
juxt
((juxt inc dec (partial * 10) even?) 1)
Write by yourslef the following funtions of clojure.core
comp (only with 2 functions)
partial (only supporting functions with 2 arguments)
comp with no limit on the number of functions
partial supporting functions any number of arguments
Elements are evaluated only when we really need them
The sequences returned by a lot of clojure
functions are lazy: map
, filter
, concat
, range
, take
...
Infinite range of numbers:
(take 10 (range))
Infinite repetition of an element:
(take 100 (repeat 1))
Infinite repetition of a sequence:
(take 100 (cycle [1 2 3]))
Infinite repetition of a function call:
(take 10 (repeatedly (partial rand-int 100)))
There is a handy macro lazy-seq
that makes it easy to create lazy sequences. Let's see it in action, by writing code that receives a sequece and increment each of its elements - without using map
:
First a non-lazy implementation - with a infinite recursion
(defn inc-seq [s]
(cons (inc (first s))
(inc-seq (rest s))))
(take 10 (inc-seq (range 100)))
Let's fix our code - by ending the recursion when the list is empty
(defn inc-seq [s]
(when (seq s)
(cons (inc (first s))
(inc-seq (rest s)))))
(take 10 (inc-seq (range 100)))
And now the lazy implementation:
(defn inc-seq-lazy [s]
(lazy-seq
(cons (inc (first s))
(inc-seq-lazy (rest s)))))
(take 10 (inc-seq-lazy (range 100)))
We can even pass an infinite lazy sequence to our function:
(take 10 (inc-seq-lazy (range)))
Write a function that creates a infinite lazy sequence of the positive integers
First write a function numbers-starting-at
that receives a number n
and returns all the numbers starting at n
(defn numbers-starting-at [n] )
(= (take 3 (numbers-starting-at 5)) '(5 6 7))
Now write a function numbers
that receive no arguments and return all the positive integers. Try to use partial
Regular recursions are dangerous as they might cause stack overflow. In clojure, there is a semantic support for tail call recursion.
At first, it's less intuitive...
But when you get used to it, it's very convenient...
Stack overflow with regular recursion
(defn count-recursive [s]
(if (empty? s) 0
(inc (count-recursive (rest s)))))
(count-recursive (range 10000))
Looks good right?
Increment the size of s
up to 1000000 and see what happens.
The solution is to use tail call recursion. Like this:
(defn count-tail-recursive [s]
(loop [s s res 0]
(if (empty? s) res
(recur (rest s) (inc res)))))
(count-tail-recursive (range 10000))
It works fine with 1000000 elements
The hard thing when writing tail call recursion is that you are not allowed to do anything after the recur
. Trying to do that will cause an exception at compile time.
(loop [s (range 100)]
(if (empty? s)
10
(inc (recur (rest s)))))
Here are more detials about recur
in clojure
Write a function that checks whether an element is present in a list
(defn present? [lst x])
Write a function that sums up the elements of a sequence. The proper solution is to use reduce
. But here, we want to practice recur
(defn sum [lst])
Write a function that receives a seuence of sequences and returns their concatanation. The proper solution would be to use (apply concat s)
; but here we want to practice recur
(defn concat-seqs [seqs])
Now, you are familiar with:
/