October 14, 2020

Clojure Goodness: Split Collection With Predicate

To split a collection in Clojure we can use the split-with and split-at functions. The split-with function takes a predicate as first argument and a colletion as second argument. The function will return a vector with two items. The first item is the result of the function take-while with the given predicate. The second item in the result vector is the resul of the drop-while function with the same predicate.

We use the split-at function with a number as first argument followed by a collection to split based on a given number of items. Instead of using a predicate we can define the number of items that we want as the first item in the result vector. The first item in the result vector is the result of invoking the take function. The resulting number of items of the collection will be the second item in the result vector and is achieved by invoking the drop function.

In the following example we use both functions with different arguments:

(ns mrhaki.core.split
  (:require [clojure.test :refer [is]]))

;; The split-with function has a predicate and returns the result 
;; of the functions take-while and drop-while in a result vector.
(let [less-than-5? (partial > 5)
      numbers      (range 11)]
  (is (= ['(0 1 2 3 4) '(5 6 7 8 9 10)]
         (split-with less-than-5? numbers))
         [(take-while less-than-5? numbers) (drop-while less-than-5? numbers)]))

;; In this example we take while the value is a String value and 
;; drop while starting from first value that is not a String.
(letfn [(string-value? [[k v]] (instance? String v))]
  (is (= ['([:language "Clojure"] [:alias "mrhaki"]) '([:age 47] [:country "NL"])]
         (split-with string-value? {:language "Clojure" :alias "mrhaki" :age 47 :country "NL"}))))

;; Instead of splitting on a predicate we can just give the number
;; of elements we want to split on with the split-at function.
(is (= ['(0 1 2 3) '(4 5 6 7)]
       (split-at 4 (range 8))
       [(take 4 (range 8)) (drop 4 (range 8))]))

(is (= ['([:language "Clojure"] [:alias "mrhaki"] [:age 47]) '([:country "NL"])]
       (split-at 3 {:language "Clojure" :alias "mrhaki" :age 47 :country "NL"})))

Clojure 1.10.1.