My solutions for the first 50 problems on 4clojure.com

For someone without previous Lisp experience, the hardest part of learning Clojure programming seems to be the functional way of doing things. It is like math, one really needs to do some exercises in order to master it. At this point, 4clojure.com seems to be the best place for getting such exercises. It has a lot of problems for new clojurians to solve. These problems ask one to fill in the blank __ so the given expressions are true. To give a little challenge, some clojure built-in functions are forbidden to use for some problems. New problems are added from time to time on the site, so it surely can keep me entertained for a while.

I just finished the first 50 problems and think it might be helpful to post the solutions here. I tried to be functional and avoided using loops in the code. Some solutions are skipped as they seem trivial even for a functional newbie like myself. My solutions are probably just awful, but it is a fun experience nevertheless. I will post more solutions when I am done with them (Solutions No.50-75 and 76-100)

Update: please find better solutions for problem 21, 27 and 44, contributed by visitors in the comments section below. Thank you guys for the nice solutions. I appreciate it.


; 21: Write a function which returns the Nth element from a sequence.
; (= (__ '(4 5 6 7) 2) 6)
; forbidden: nth
(fn [coll n] 
  ((apply comp (cons first (repeat n rest))) coll))
; We first compose n rest functions to get progressively shorter lists till the
; desired element is the head, then take the head. A less fancy version just
; uses nthnext, but it feels like cheating:
(fn [coll n]
  (first (nthnext coll n)))

; 22: Write a function which returns the total number of elements in a sequence.
; (= (__ '(1 2 3 3 1)) 5)
; forbidden: count
#(reduce + (map (fn [x] 1) %))
; We just turn each element into 1 and then add them up
; Note that (fn [x] 1) can be replaced by (constantly 1)

; 23: Write a function which reverses a sequence.
; (= (__ [1 2 3 4 5]) [5 4 3 2 1])
; forbidden: reverse
#(into () %)
; We exploit the property of the list, which alway add new element
; in front of the head. Also that the clojure sequences' equality
; evaluation is element based, so [1 2 3] equals to '(1 2 3)

; 26: Write a function which returns the first X fibonacci numbers.
; (= (__ 6) '(1 1 2 3 5 8))
(fn [x]
  (take x
    ((fn fib [a b]
        (cons a (lazy-seq (fib b (+ a b))))) 
      1 1))) 
; we first recursively construct a lazy sequence of infinite number of
; fibonacci numbers

; 27: Write a function which returns true if the given sequence is a palindrome.
; (true? (__ '(1 1 3 3 1 1)))
(fn [coll]
  (let [rc (reverse coll) n (count coll)]
    (every? identity 
      (map #(= (nth coll %) (nth rc %)) (range (/ (dec n) 2))))))
; we naively compare half of the pairs of elment e(i) and e(n-i-1)

; 28: Write a function which flattens a sequence.
; (= (__ '((1 2) 3 [4 [5 6]])) '(1 2 3 4 5 6))
; forbidden: flatten
(fn flt [coll]
  (let [l (first coll) r (next coll)]
    (concat 
      (if (sequential? l)
        (flt l)
        [l])
      (when (sequential? r)
        (flt r)))))
; we basically treat the nested collection as a tree and recursively walk the
; tree. Clojure's flatten use a tree-seq to walk the tree.

; 29: Write a function which takes a string and returns a new string containing
;     only the capital letters.
; (= (__ "HeLlO, WoRlD!") "HLOWRD")    
(fn [coll]
  (apply str (filter #(Character/isUpperCase %) coll)))
; note the use of apply here, as str takes a number of args instead
; of a character collection

; 30: Write a function which removes consecutive duplicates from a sequence.
;  (= (apply str (__ "Leeeeeerrroyyy")) "Leroy")
(fn cmprs [coll]
  (when-let [[f & r] (seq coll)] 
    (if (= f (first r)) 
      (cmprs r) 
      (cons f (cmprs r)))))  
; Basically a variant of the filter function. Note the sequence is destructed
; into first element f and the rest r.

; 31: Write a function which packs consecutive duplicates into sub-lists.
; (= (__ [1 1 2 1 1 1 3 3]) '((1 1) (2) (1 1 1) (3 3)))
(fn [coll]
  ((fn pack [res prev coll]
    (if-let [[f & r] (seq coll)] 
      (if (= f (first prev)) 
        (pack res (conj prev f) r) 
        (pack (conj res prev) [f] r))) 
     (conj res prev))
    [] [(first coll)] (rest coll)))  
; res is the final list, prev keeps the immediate previous sub-list.
; A much simpler version use partition-by:
#(partition-by identity %)

; 33: Write a function which replicates each element of a sequence n number of
; times.
; (= (__ [1 2 3] 2) '(1 1 2 2 3 3))
(fn [coll n]
  (apply concat (map #(repeat n %) coll)))
; or more succintly:
(fn [coll n]
  (mapcat #(repeat n %) coll))

; 34: Write a function which creates a list of all integers in a given range.
; (= (__ 1 4) '(1 2 3))
; forbidden: range
(fn [s e]
  (take (- e s) (iterate inc s)))

; 38: Write a function which takes a variable number of parameters and returns
; the maximum value.
; forbidden: max, max-key
; (= (__ 1 8 3 4) 8)
(fn [x & xs]
  (reduce #(if (< %1 %2) %2 %1) x xs))

; 39: Write a function which takes two sequences and returns the first item
; from each, then the second item from each, then the third, etc.
; (= (__ [1 2] [3 4 5 6]) '(1 3 2 4))
; forbidden: interleave
#(mapcat vector %1 %2) 

; 40: Write a function which separates the items of a sequence by an arbitrary
; value.
; (= (__ 0 [1 2 3]) [1 0 2 0 3])
; forbidden: interpose
(fn [sep coll]
  (drop-last (mapcat vector coll (repeat sep))))

; 41: Write a function which drops every Nth item from a sequence.
; (= (__ [1 2 3 4 5 6 7 8] 3) [1 2 4 5 7 8])  
(fn [coll n]
  (flatten 
    (concat 
      (map #(drop-last %) (partition n coll)) 
      (take-last (rem (count coll) n) coll))))
; We partition the sequence, drop last one from each, then stitch them back
; take care the remaining elements too

; 42: Write a function which calculates factorials.
; (= (__ 5) 120)
(fn [n]
  (apply * (range 1 (inc n))))
; clojure arithmetic functions can take a variable number of arguments

; 43: Write a function which reverses the interleave process into n number of
; subsequences.
; (= (__ [1 2 3 4 5 6] 2) '((1 3 5) (2 4 6)))
(fn [coll n]
  (apply map list (partition n coll)))
; exploit map function's ability to take a variable number of collections as
; arguments

; 44: Write a function which can rotate a sequence in either direction.
; (= (__ 2 [1 2 3 4 5]) '(3 4 5 1 2))
; (= (__ -2 [1 2 3 4 5]) '(4 5 1 2 3))
(fn [n coll]
  (let [ntime (if (neg? n) (- n) n)
        lshift #(concat (rest %) [(first %)])
        rshift #(cons (last %) (drop-last %))]
    ((apply comp (repeat ntime (if (neg? n) rshift lshift))) coll)))

; 50: Write a function which takes a sequence consisting of items with different
; types and splits them up into a set of homogeneous sub-sequences. The internal
; order of each sub-sequence should be maintained, but the sub-sequences
; themselves can be returned in any order (this is why 'set' is used in the
; test cases).
; (= (set (__ [1 :a 2 :b 3 :c])) #{[1 2 3] [:a :b :c]})
#(vals (group-by type %))

Comments

Andro's picture

Problem 21

I checked the code of drop and this is the solution I used.

(fn element-at [list n]
(let [go (fn [list n] (if (and (pos? n) list) (recur (rest list) (dec n)) list))]
(first(go list n))))

Kevin Litwack's picture

(slightly pedantic) bug in solution #30

Here's the bug:

user=> (cmprs [1 1 2 2 nil nil])
(1 2)

Because (first r) returns nil if r is emtpy, any trailing nils in the input collection will be lost. This fixes it:

(defn cmprs2 [coll]

  (when-let [[f & r] (seq coll)]
    (if (and (nil? f) (empty? r))
      (list f)

      (if (= f (first r))
        (cmprs2 r)
        (cons f (cmprs2 r))))))

user=> (cmprs2 [1 1 2 2 nil nil])
(1 2 nil)

Anonymous's picture

a simpler solution for

a simpler solution for #28:
(fn flat [x] (apply concat (map #(if (sequential? %) (flat %) [%]) x)))

Anonymous's picture

Problem 44

I have a solution that I believe to be just a bit more concise than the previous comment.

(fn rotate[n seq]
  (let [n (mod n (count seq))]
    (concat (drop n seq) (take n seq))))
cybergrind's picture

Problem 27

27. (fn [what] (= (reverse what) ((comp reverse reverse) what)))

Max Bernstein's picture

For program 40

#(mapcat vector (repeat (count %2) %1) %2)

You can use your previous solution, and mapcat (or interleave) 2 seqs.

Max Bernstein's picture

Sorry, the correct and better way:

#(drop-last (interleave %2 (repeat (count %2) %1)))

Max Bernstein's picture

Problem 30 - compression

#(map first (partition-by identity %))

`partition-by identity` separates into a seq separated by sections of elements. then you get the first of each section and return the seq.

Max Bernstein's picture

Problem 27 - palindrome?

(fn [x] (= (reverse x) (seq s)))

Max Bernstein's picture

Problem 26 - fibonacci

Mine is a bit more verbose...
(fn [ind] (map (fn fib [x] (if (<= x 2) 1 (+ (fib (- x 1)) (fib (- x 2))))) (map #(+ 1 %) (range ind))))

Basically, make a fibonacci function, map it to a range offset by 1.

Max Bernstein's picture

For exercise number 21... could be done shorter

#(.get %1 %2)

Kresimir Bojcic's picture

#21

#( ( vec %) %2)

Huahai's picture

Neat

This solution takes advantage of the fact that a Clojure vector is a function. This function returns an element of the vector with the given index.

Thanks for contributing this one. Love it.  

Anonymous's picture

problem #21 - your solution

problem #21 - your solution is confusing..simple one below

(fn[col x ]
(first (drop x col)))

Huahai's picture

duplicate

citizen428 has already posted the same solution. Thank you for stopping by.

bjc's picture

I wish they would put some of these up alongside the problems

I really love how elegantly you have solved these solutions, far better than my attempts (the functional way of thinking still makes my brain hurt) Thanks for taking the time to document your thought processes along with the problems. I've found it more useful than reading documentation as the problem domain is more succinct.

Thanks!

Huahai's picture

Thank you for your nice words

I am glad that this is helpful.  Happy Clojuring Cool

x_rex's picture

Problem 27

Solution to the problem 27 is as simple as:
#(= (seq %) (reverse %))

Huahai's picture

Good one

This is a nice example of the difference between functional and imperative thinking. Here the thinking is on the collection level,  not element-wise comparison, like an imperative programmer would do.  I guess it takes a lot to unlearn twenty years of imperative programming Smile

citizen428's picture

The solution to problem 21

The solution to problem 21 seems a bit too complicated:

(fn [coll n] (first (drop n coll)))

Huahai's picture

agreed

You are right, that solution is too complicated. When I wrote that, I had not been used to the functional way of thinking, which is more high level and declarative. As you can see, that solution was still trying to do things sequentially, basically trying to do a loop by function composition. Your solution is more declarative: just drops the last n elements and does not care about how they are dropped.

Another lesson is that one really needs to get acquainted with seq library of Clojure. I did not know about the drop function when I wrote my solution.

Thanks for sharing your solution. I appreciate it.

Bob Shock's picture

Problem 44

I struggled with that one a lot. My first solution was about twice as long as yours. Then I figured out it was simply a matter of splitting the collection in half at the right place, then joining the two pieces back together in the opposite order.

#(let [
       [l r] (split-at (mod % (count %2)) %2)]
    (concat r l))
Huahai's picture

very nice

Yours is short and sweet. I didn't think in that way. split-at is good to know too. Thank a lot.

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <div> <h1><h2><h3><sub><sup><b><i><u><font><img>
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Lines and paragraphs break automatically.

More information about formatting options

To prevent automated spam submissions leave this field empty.
Nice place