Lazy Sequences
Create a lazy sequence by repeating a value:
(def chorus (repeat "oh"))
Such a sequence is lazy by providing its values on demand, and it is unbounded by providing values forever:
(first chorus) ; "oh"
(nth chorus 10) ; "oh"
(nth chorus 1000) ; "oh"
Retrieve the first n
elements of a sequence:
(take 3 chorus) ; ("oh" "oh" "oh")
Repeat the items of a sequence:
(def verse (cycle ["oh" "ah" "yeah"]))
(take 7 verse) ; ("oh" "ah" "yeah" "oh" "ah" "yeah" "oh")
Not the sequence itself, but its items are repeated in a cycle.
Create a sequence based on a starting value and a successor function:
(def counter (iterate inc 1))
(take 5 counter) ; (1 2 3 4 5)
The generated sequence is immutable and stateless:
(first counter) ; 1
(first counter) ; 1
(take 5 counter) ; (1 2 3 4 5)
(take 3 counter) ; (1 2 3)
Many functions on sequences are lazy:
(take 5 (map #(* 10 %) (iterate inc 1))) ; (10 20 30 40 50)
(take 3 (filter #(= (mod % 2) 0) (iterate inc 1))) ; (2 4 6)
The values are only mapped and filtered as needed.
While many functions on sequences are lazy, some like count
and
reduce
are eager.
Create a lazy sequence explicitly from another (non-lazy) sequence:
(def nums (lazy-seq [1 2 3]))
(take 2 nums) ; (1 2)
(take 3 nums) ; (1 2 3)
Limit the REPL output size when dealing with infinite sequences:
(set! *print-length* 5)
(println (iterate inc 1)) ; (1 2 3 4 5 ...)
Realize a lazy sequence, enforcing its computation:
(defn chatty-inc [n]
(println "incrementing" n)
(inc n))
(def counter (iterate chatty-inc 1)) ; no output
(def first-5 (take 5 counter)) ; still no output
(doall first-5)
;; incrementing 1
;; incrementing 2
;; incrementing 3
;; incrementing 4
;; incrementing 5
;; (1 2 3 4 5)
Process the elements of a lazy sequence one by one:
(defn decorate [xs]
(doseq [x xs]
(println "<[" x "]>")))
(decorate (take 3 (iterate inc 1)))
;; <[ 1 ]>
;; <[ 2 ]>
;; <[ 3 ]>
Write a function that produces a lazy sequence:
(defn my-iterate [f x]
(let [next (f x)]
(cons x (lazy-seq (my-iterate f next)))))
(take 5 (my-iterate inc 1)) ; (1 2 3 4 5)
(take 5 (my-iterate #(* % 2) 1)) ; (1 2 4 8 16)
Exercises
Lazy Fibonacci Numbers
Write a function lazy-fib
that returns a lazy sequence of Fibonacci
numbers.
Hint: Use cons
and lazy-seq
with a recursive function call to
produce the next element. A multi-arity function (with zero and two
arguments) is a good option to provide the initial two Fibonacci
numbers.
Test: (take 10 (fib-stream))
shall return (1 1 2 3 5 8 13 21 34 55)
, and (nth (fib-stream) 45)
shall return 1836311903
.
Lazy Prime Numbers
Write a function lazy-primes
that returns a lazy sequence of prime
numbers. Write a predicate function is-prime
that checks whether or
not the given argument is a prime number.
Hint: Use cons
and lazy-seq
again. Use the functions take-while
and some
to create and process the candidates for the divisibility
test in the is-prime
predicate.
Test: (take 10 (lazy-primes))
shall return (2 3 5 7 11 13 17 19 23 29)
.
Process File Line by Line
Write a function sum-up-to
that reads lines from a text file, tries
to parse them as numbers, sums those numbers up until a given limit is
reached, writes the processed numeric lines into a output text file,
and returns the sum. Both source
and target
paths and the limit
are given as parameters.
Hint: Use with-open
, clojure.java.io/reader
,
clojure.java.io/writer
, and line-seq
for (lazy) file line by line
processing. If the lines are exhausted before the limit is reached,
the current sum should be returned.
Test: Given the following file lines.txt
:
1
foo
2
bar
3
qux
4
baz
5
bum
6
wah
7
(sum-up-to lines.txt result.txt 16)
shall return 15
and produce a
file result.txt
with the following content:
1
2
3
4
5