Search

Dark theme | Light theme
Showing posts with label ClojureGoodness:Sequences. Show all posts
Showing posts with label ClojureGoodness:Sequences. Show all posts

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.

June 8, 2020

Clojure Goodness: Transforming Collection Items With Index

If we want to transform items in a collection we can use the map function. If we also want to use the index of the element in the collection in the transformation we must use the map-indexed function. We must provide a function with 2 arguments, where the first argument is the index of the element in the collection and the second argument is the element in the collection.

In the following examples we use the map-indexed function:

(ns mrhaki.core.map-indexed
  (:require [clojure.test :refer [is]]))

;; map-indexed applies a function to each element
;; in a collection where the function gets the
;; index of the item in the collection and the item itself.
(is (= [[0 3] [1 20] [2 10] [3 2] [4 1]]
       (map-indexed (fn [index number] [index number]) [3 20 10 2 1])))


(defn indices
  "Return lazy sequence of indices of elements in a collection."
  [coll]
  (map-indexed (fn [index _] index) coll))

(is (= [0 1 2 3 4] (indices [3 20 10 2 1])))


(defn char-range
  "Function to return a range of characters from `start` to `end` (including)."
  [start end]
  (map char (range (int start) (inc (int end)))))

(def a-z (char-range \a \z)) ;; characters from a to z.

;; map-indexed returns a lazy sequence.
(is (= [[\a 0] [\b 1] [\c 2]]
       (take 3 (map-indexed (fn [index ch] [ch index]) a-z))))


;; Create map with letter keys and position in alphabet as values.
(def letters-positions (into {} (map-indexed (fn [index ch] [ch (inc index)]) a-z)))

(is (= [[\a 1] [\b 2] [\c 3]]
       (take 3 letters-positions)))

;; Find position of each letter of word "clojure".
(is (= [3 12 15 10 21 18 5]
       (reduce (fn [result value] (conj result (get letters-positions value)))
               []
               "clojure")))

Written with Clojure 1.10.1.

Clojure Goodness: Repeating A Value Or Function Invocation

In Clojure we can use the repeat function to get an infinite sequence of a given value. We can pass a length argument to get a fixed size sequence of the value. Clojure also provides the repeatedly function that takes as argument a function without arguments. A infinite sequence of invocations of the function is returned. Just like with the repeat function we can pass a length argument so the returned sequence has a fixed size.

We use the repeat and repeatedly function in the following example:

(ns mrhaki.core.repeat
  (:require [clojure.test :refer [is]])
  (:import (java.time LocalTime)))


;; repeat creates an infinite sequence of the given value.
(is (= ["Clojure" "Clojure" "Clojure"] 
       (take 3 (repeat "Clojure"))))

;; We can pass a length argument to restrict the number of 
;; times the value is repeated.
(is (= ["rocks" "rocks" "rocks"]
       (repeat 3 "rocks")))

(defn parrot-talk 
  [s]
  (repeat 2 s))

(is (= ["Polly wants a cracker" "Polly wants a cracker"]
       (parrot-talk "Polly wants a cracker")))


(defn before?
  "Helper function returns true if t1 is before t2, false otherwise"
  [[^LocalTime t1 ^LocalTime t2]]
  (.isBefore t1 t2))

(defn current-time 
  "Return current time"
  []
  (LocalTime/now))

;; repeatedly create an infinite sequence of function invocations.
;; The function must have no arguments.
(is (before? (take 2 (repeatedly current-time))))

(is (before? (repeatedly 2 current-time)))


(defn latest-time
  "Get the 'latest' time from a collection with time values."
  [coll]
  (reduce (fn [latest time] (if (.isAfter latest time) latest time)) coll))

(def current-times (repeatedly 100 current-time))

;; as each time is later than the next time value
;; the following equation must be true.
(is (= (latest-time current-times) (last current-times)))

Written with Clojure 1.10.1.

June 2, 2020

Clojure Goodness: Repeat Items In A Collection As Lazy Sequence With cycle

The Clojure function cycle take a collections as argument and creates a lazy sequence by repeating the items in the collection. So if we pass a collection with the characters \a, \b and \c we get a lazy sequence of (\a \b \c \a \b \c ...).

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

;; The items in the collection are repeated
;; and return type is a lazy sequence.
(is (= [0 1 0 1 0 1] 
       (take 6 (cycle [0 1]))))
(is (seq? (cycle [0 1])))

(is (= [\C \l \o \j \u \r \e \C \l \o \j \u \r \e]
       (take 14 (cycle "Clojure"))))

;; Useful for functions that want equally sized 
;; collection arguments.
(is (= {:a 0 :b 1 :c 0 :d 1} 
       (zipmap [:a :b :c :d] (cycle [0 1]))))

Written with Clojure 1.10.1.

May 29, 2020

Clojure Goodness: Interleave Keys And Values Into A Map With zipmap

The Clojure function zipmap create a map by interleaving a collection of keys with a collection of values. The first element of the keys collection is the map entry keyword and the first element of the values collection is than the map entry value, and so on for the other elements in the keys and values collections.

In the following example code we use zipmap using different examples of keys and values collections:

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

;; zipmap creates a map with keys from the
;; first collection and values from the second.
(is (= {:name "Hubert" :alias "mrhaki"} 
       (zipmap [:name :alias] ["Hubert" "mrhaki"])))

;; If the size of the values collection is smaller
;; than the size of the keys collection, only 
;; keys that map to a value end in up
;; in the resulting map. 
(is (= {:name "Hubert"}
       (zipmap [:name :alias] ["Hubert"])))

;; If the size of the keys collection is smaller
;; than the size of the value collection, then the
;; returned map only contains keys from the keys 
;; collection and some values are ignored.
(is (= {:name "Hubert"}
       (zipmap [:name] ["Hubert" "mrhaki"])))

;; Using a lazy sequence created by the repeat
;; function we can set a default value for all keys.
(is (= {:name "" :alias "" :city ""}
       (zipmap [:name :alias :city] (repeat ""))))

;; If we have keys with the same name the last
;; mapping ends up in the resulting map.
(is (= {:name "mrhaki"}
       (zipmap [:name :name] ["Hubert" "mrhaki"])))

;; Keys for the resulting map don't have to be keywords,
;; but can be any type.
(is (= {"name" "Hubert" "alias" "mrhaki"}
       (zipmap ["name" "alias"] ["Hubert" "mrhaki"])))

Written with Clojure 1.10.1.

May 13, 2020

Clojure Goodness: Using The range Function

In Clojure we can use the range function to create a lazy sequence of numbers. We can optionally specify a start value, end value and define the steps between the numbers. If we use the end value argument that value is exclusive for the returned values in the lazy sequence.

In the following example we invoke the range function with different arguments:

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

;; range function without arguments returns
;; an infinite lazy sequence of numbers.
(is (= '(0 1 2 3 4) (take 5 (range))))

;; We can specifyt the start value for 
;; a lazy infinite  sequence of numbers.
(is (= '(0 1 2 3 4) (range 5)))

;; With the second argument we set the
;; end value for our lazy sequence of numbers.
;; The end value is exclusive for the range.
(is (= '(5 6 7 8 9) (range 5 10)))

;; The third argument defines the step value
;; between numbers, which by default is 1.
(is (= '(0 2 4 6 8) (range 0 10 2)))

;; We can also have a lazy sequence counting 
;; numbers back.
(is (= '(5 4 3 2 1) (range 5 0 -1)))

(is (= '(100 97 94 91 88) (take 5 (range 100 0 -3))))

Written with Clojure 1.10.1.

April 13, 2020

Clojure Goodness: Combine First And Next Functions Multiple Times

The first function in Clojure returns the first item of a collection. The next function returns a new sequence with all elements after the first element from a collection. Clojure adds some utility methods to combine first and next with different combinations. We can use the function ffirst which is will return the first element of the first element of a collection and the nfirst function to get the next elements from the first element of a collection. We can use the function fnext to get the first element of the next elements of a collection and the function nnext to get the next elements of the next elements of a collection.

In the following example we use the ffirst, nfirst, fnext and nnext:

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

(def langs [{:language "Clojure" :site "https://clojure.org" :dynamic true}
            {:language "Groovy" :site "https://www.groovy-lang.org" :dynamic true}
            {:language "Java" :site "https://www.java.com" :dynamic false}
            {:language "Kotlin" :site "https://kotlinlang.org" :dynamic false}])

;; Find first map entry of first map in languages.
(is (= [:language "Clojure"]
       (ffirst langs)
       (first (first langs))))

;; Find next map entries for first map in languages.
(is (= (list [:site "https://clojure.org"] [:dynamic true])
       (nfirst langs)
       (next (first langs))))

;; Find first map of next maps in languages.
(is (= {:language "Groovy" :site "https://www.groovy-lang.org" :dynamic true}
       (fnext langs)
       (first (next langs))))

;; Find next maps of next maps in languages.
(is (= (list {:language "Java" :site "https://www.java.com" :dynamic false}
             {:language "Kotlin" :site "https://kotlinlang.org" :dynamic false})
       (nnext langs)
       (next (next langs))))

Written with Clojure 1.10.1.

March 30, 2020

Clojure Goodness: Get Random Item From A Sequence

In Clojure we can use the rand-nth function to get a single random element from a sequence. To get multiple items based on random probability for each item we use the function random-sample. We must set the probability that determines for each item if it is in the result or not.

In the following example code we use rand-nth function:

(ns mrhaki.seq.random
  (:require [clojure.test :refer [is]]))

;; We use the function rand-nth to get a 
;; random element from a sequence collection.
(is (contains? #{"Clojure" "Java" "Groovy"} 
               (rand-nth ["Groovy", "Clojure", "Java"])))

;; We can use the rand-nth function with a map
;; if we first turn it into a sequence.
(is (contains? #{[:a 1] [:b 2]} (rand-nth (seq {:a 1 :b 2}))))

This next example shows how we can use the random-sample function:

(ns mrhaki.seq.random
  (:require [clojure.test :refer [is]]))

;; Using random-sample each item is in the
;; result based on the random probability of the
;; probability argument. 
;; When probability is 1 all items are returned.
(is (= ["Clojure" "Groovy" "Java"]
       (random-sample 1.0 ["Clojure" "Groovy" "Java"])))

;; When proability is 0 no item is in the result.
(is (empty? (random-sample 0 ["Clojure" "Groovy" "Java"])))

;; Any other value between 0 and 1 will return different
;; results for each invocation of the random-sample function.
(def samples (random-sample 0.4 ["Clojure" "Groovy"]))
(is (or (empty? samples)
        (= ["Clojure" "Groovy"] samples)
        (= ["Clojure"] samples)
        (= ["Groovy"] samples)))

Written with Clojure 1.10.1