Solving Clojure problems on 4ever-clojure (44/151)

2022-01-04

I've started to learn Clojure by solving the problems over at 4ever-clojure. There are currently 151 problems on that site. I just solved number 44 and thought I'd take some time to reflect.

The conj function confused me at first because I didn't understand its purpose, which is to ask a collection to make a new collection with an element added to it in a place that makes sense to the collection. When the collection is a vector it makes sense to add the element to the end of it, but for sequences it's more efficient to add it to the front.

Problem 30 is about removing consecutive duplicates from a sequence. For example, '(9 3 3 2 1 1 1 3 1) should become '(9 3 2 1 3 1). Here's how I solved it.

(def p30a
  (fn [x]
    (reverse
     (loop [source x acc '()]
       (if (empty? source)
         acc
         (if (= (first source) (second source))
           (recur (drop 1 source) acc)
           (recur (drop 1 source) (conj acc (first source)))))))))

Needlessly complicated, you say? I agree. "I'll come back to this later and try to write a shorter version", was what I was going to say but of course I couldn't let it go so here's my second attempt.

(def p30b
  (fn [A]
    (reverse
     (reduce
      #(if (= (first %1) %2) %1 (conj %1 %2))
      '()
      A))))

I got rid of one loop and two recur:s at the cost of introducing one reduce, which is an improvement, I think.

Problem 31: pack consecutive duplicates into sub-lists. For example, '(1 1 2 1 1 1 3 3) should become '((1 1) (2) (1 1 1) (3 3)). Not too happy with how I solved this one either.

(def p31
  (fn [x]
    (reverse
     (loop [lst x, acc '()]
       (if (empty? lst)
         acc
         (let [dacc (take-while (partial = (first lst)) lst)]
           (recur (drop (count dacc) lst) (conj acc dacc))))))))

I was really into loop and recur at this point. It looks terrible, there must be a much simpler solution but I'll leave it for now.

Problem 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. I managed to produce a good solution to this one, I think.

(def p39
  (fn [A B]
    (mapcat vector A B)))

Looks great, if I do say so myself. I got there by realizing that a map function can operate on multiple collections at once, and then I just happened to find mapcat which conveniently concat:s the map result.

Problem 41: drop every Nth item from a sequence. I don't love what I came up with.

(def p41
  (fn [A n]
    (map
     #(nth A %)
     (filter
      #(< 0 (mod (inc %) n))
      (range (count A))))))

At least I'm just using plain functions like map and filter.

Problem 42: factorial function. Another one I'm pretty proud of.

(def p42
  (fn [x]
    (reduce * (map inc (range x)))))

I have no idea how that could be improved but we'll see.

Problem 43: reverse the interleave process into x number of subsequences. For example, '(1 2 3 4 5 6) 2 should become '((1 3 5) (2 4 6)). This is the first "medium" difficulty problem I solved, all the others were "easy" or "elementary".

(def p43
  (fn [A n]
    (let [steps (filter #(= 0 (mod % n)) (range (count A)))]
      (for [i (range n)]
        (map #(nth A (+ i %)) steps)))))

I'm not loving the for call, feels like there should be a cleaner solution.

Problem 44: rotate a sequence in either direction. For example, 2 '(1 2 3 4 5) should become '(3 4 5 1 2), and -2 '(1 2 3 4 5) should become '(4 5 1 2 3). My first solution p44a felt a little clunky so I wrote p44b that uses a different approach.

(def p44a
  (fn [d A]
    (let [nA (count A)
          s (map #(mod (+ % d) nA) (range nA))]
      (map #(nth A %) s))))

(def p44b
  (fn [d A]
    (let [x (mod d (count A))]
      (concat (drop x A) (take x A)))))