{:check ["true"], :rank ["create" "destructuring" "transform"]}

Index

Functions on Hash-maps

Create

Creating hash-map

In [6]:
{:name "Albert Einstein"
 :profession "Physicist"
 :date-of-birth "March 14, 1879"}
Out[6]:
{:name "Albert Einstein", :profession "Physicist", :date-of-birth "March 14, 1879"}
In [7]:
{"Toronto" '(3 :million)
 "Oshawa" '(150 :thousand)}
Out[7]:
{"Toronto" (3 :million), "Oshawa" (150 :thousand)}
In [9]:
(hash-map :name "Albert Einstein" 
          :profession "Physicist")
Out[9]:
{:name "Albert Einstein", :profession "Physicist"}
In [2]:
(zipmap [:name :profession] ["Albert Einstein" "Physicist"])
Out[2]:
{:name "Albert Einstein", :profession "Physicist"}

Destructuring

In [5]:
(require '[clojure.pprint :refer [pprint]])
Out[5]:
nil

Destructuring

In [1]:
(def person {:name "Albert Einstein"
             :profession "Physicist"
             :date-of-birth {:year 1879
                             :month "March"
                             :day 14}
             :date-of-death {:year 1955
                             :month "April"
                             :day 18}
             :achievements [{:type "Theory" :title "Special Relativity"}
                            {:type "Theory" :title "General Relativity"}
                            {:type "Equation" :title "Einstein Field Equations"}
                            {:type "Prize" :title "Nobel Prize" :year 1921}
                            ]})
Out[1]:
#'user/person

List the keys

In [8]:
(keys person)
Out[8]:
(:name :profession :date-of-birth :date-of-death :achievements)

List the values

In [10]:
(pprint (vals person))
("Albert Einstein"
 "Physicist"
 {:year 1879, :month "March", :day 14}
 {:year 1955, :month "April", :day 18}
 [{:type "Theory", :title "Special Relativity"}
  {:type "Theory", :title "General Relativity"}
  {:type "Equation", :title "Einstein Field Equations"}
  {:type "Prize", :title "Nobel Prize", :year 1921}])
Out[10]:
nil
In [11]:
; Hashmaps are functions
; key -> value

(person :name)
Out[11]:
"Albert Einstein"
In [12]:
; Many other ways of getting the value based on the index
(get person :name)
Out[12]:
"Albert Einstein"
In [14]:
; Keywords are __functions__ of hashmap to value
; hashmap -> value
(:name person)
Out[14]:
"Albert Einstein"
In [18]:
; nil signifies non-existing keys
(get person :last-name)
Out[18]:
nil
In [19]:
(person :last-name)
Out[19]:
nil
In [20]:
(:last-name person)
Out[20]:
nil

Membership check

In [16]:
(contains? person :name)
Out[16]:
true
In [17]:
(contains? person :last-name)
Out[17]:
false

Deep destructuring

In [22]:
; nested destructuring using get
(get (get person :date-of-birth) :month)
Out[22]:
"March"
In [23]:
; nested destructuring using keyword as function
(:month (:date-of-birth person))
Out[23]:
"March"
In [24]:
; nested destructuring using hash-map as function
((person :date-of-birth) :month)
Out[24]:
"March"
In [25]:
; get-in with key path
(get-in person [:date-of-birth :month])
Out[25]:
"March"

Composing functions on hash-maps

In [31]:
; Title of first achievement
(:title (first (person :achievements)))
Out[31]:
"Special Relativity"
In [33]:
; Title of first achievement
; Here we use the fact that vectors are functions too.

(((person :achievements) 0) :title)
Out[33]:
"Special Relativity"
In [34]:
; get-in is a very useful function for deep navigation into
; nested data
(get-in person [:achievements 0 :title])
Out[34]:
"Special Relativity"

Transform

Construction and other transformations

In [17]:
(require '[clojure.pprint :refer [pprint]])
Out[17]:
nil

Association

In [2]:
;
; Associating a new key value pair to hash-map
;
; assoc is a function that sets key/value pair:
; hash-map, key, val -> hash-map

(assoc {:a 1 :b 2} :c 3)
Out[2]:
{:a 1, :b 2, :c 3}
In [3]:
;
; assoc can be used to set a new value to an existing key
;
(assoc {:a 1 :b 2} :b 3)
Out[3]:
{:a 1, :b 3}
In [4]:
;
; Clojure transformations are pure, namely they
; do not change the original hash-map.
;

(let [x {:a 1 :b 2}
      y (assoc x :b 3)]
    (println "x=" x)
    (println "y=" y))
x= {:a 1, :b 2}
y= {:a 1, :b 3}
Out[4]:
nil

In the previous example, we see that x remains the same, even after assoc. So, assoc only transforms x into y.

In [37]:
;
; We can associate multiple key/value pairs in one `(assoc ...)`
;
(assoc {:a 1 :b 2}
       :c 3
       :d 4
       :e 5)
Out[37]:
{:a 1, :b 2, :c 3, :d 4, :e 5}

Dissociation

In [6]:
;
; Dissociating a key from a hash-map
;

(dissoc {:a 1 :b 2} :a)
Out[6]:
{:b 2}
In [7]:
;
; Dissociating a non-existing key does nothing
;
(dissoc {:a 1 :b 2} :c)
Out[7]:
{:a 1, :b 2}

Note: dissoc, just like assoc, does not change the input hash-map. It always produces a new hash-map.

Update

  • Suppose that we have a hash-map, $m$, which has some key $k$ and value $v$: $$ m = \left\{\begin{array}{c} k \to v \\ \vdots \\ \end{array}\right\}$$
  • If we have a function $f: v\to v_\mathrm{new}$, then we can construct the following hashmap: $$ m = \left\{\begin{array}{c} k \to f(v) \\ \vdots \\ \end{array}\right\}$$
  • This is done by the update function: $$\mathrm{update} : \mathrm{hashmap}, k, f \to \mathrm{hashmap}$$
In [8]:
;
; Updating the value of an existing key
;
(update {:a 1 :b 2} :b inc)
Out[8]:
{:a 1, :b 3}
In [9]:
;
; Update :b by multiplication with 1000
;
(update {:a [1 2] :b 3} :b (fn [v] (* 1000 v)))
Out[9]:
{:a [1 2], :b 3000}
In [12]:
;
; Update :a by adding two strings to the vector
;
(update {:a [1 2] :b 3} 
        :a 
        (fn [xs] (into xs ["Hello" "World"])))
Out[12]:
{:a [1 2 "Hello" "World"], :b 3}
In [18]:
;
; Update-in performs keep update in nested data
;
(pprint
(update-in {:name "Albert Einstein"
            :date-of-birth {:year 1879
                            :month "March"
                            :day 17}}
           [:date-of-birth :year]
           (fn [y]
               (let [num-years (- 2020 y)]
                   (format "%d years ago" num-years)))))
{:name "Albert Einstein",
 :date-of-birth {:year "141 years ago", :month "March", :day 17}}
Out[18]:
nil

Merging multiple hash-maps

In [19]:
;
; Merging multiple hashmaps into one
;
(merge {:a 1 :b 2} {:c 3 :b 4})
Out[19]:
{:a 1, :b 4, :c 3}

Merge hashmap with merge fn

Suppose we have two hashmap with overlapping keys.

$$ m_1 = \left\{\begin{array}{c} x_1 \to a_1 \\ x_2 \to a_2 \\ y_1 \to b_1 \\ y_2 \to b_2 \end{array}\right\} \quad m_1 = \left\{\begin{array}{c} x_1 \to a_3 \\ x_2 \to a_4 \\ z_1 \to b_3 \\ z_2 \to b_4 \end{array}\right\} $$

Let a function $f = (\lambda x.y. \dots)$

We can use $(\mathtt{merge-with}\ f\ m_1 \ m_2)$ to obtain:

$$ \left\{\begin{array}{c} x_1 \to f(a_1, a_3) \\ x_2 \to f(a_2, a_4) \\ y_1 \to b_1 \\ y_2 \to b_2 \\ z_1 \to b_3 \\ z_2 \to b_4 \end{array}\right\} $$
In [23]:
(merge-with + {:a 1 :b 2} {:c 3 :b 4})
Out[23]:
{:a 1, :b 6, :c 3}
In [25]:
(merge-with list {:a 1 :b 2} {:c 3 :b 4})
Out[25]:
{:a 1, :b (2 4), :c 3}
In [27]:
(merge-with 
 (fn [x y] (format "%d <-> %d" x y)) 
 {:a 1 :b 2} 
 {:c 3 :b 4})
Out[27]:
{:a 1, :b "2 <-> 4", :c 3}

Into hashmap with a list of key/value pairs

In [29]:
(into {:a 1 :b 2}
      [[:last-name "Albert"]
       [:first-name "Einstein"]])
Out[29]:
{:a 1, :b 2, :last-name "Albert", :first-name "Einstein"}

Example

In [40]:
(def person {:name "Albert Einstein"
             :profession "Physicist"
             :date-of-birth {:year 1979         ; <=== should be 1879
                             :month "March"
                             :day "Unknown"}    ; <=== should be one less
             :date-of-death {:year 1955
                             :month "April"
                             :day 18}})
Out[40]:
#'user/person

Fixing the birth year

In [41]:
(pprint
 (update-in person [:date-of-birth :year] #(- % 100)))
{:name "Albert Einstein",
 :profession "Physicist",
 :date-of-birth {:year 1879, :month "March", :day "Unknown"},
 :date-of-death {:year 1955, :month "April", :day 18}}
Out[41]:
nil

Fixing the day

In [42]:
(pprint
 (assoc-in
  (update-in person [:date-of-birth :year] #(- % 100))
  [:date-of-birth :day]
  14))
{:name "Albert Einstein",
 :profession "Physicist",
 :date-of-birth {:year 1879, :month "March", :day 14},
 :date-of-death {:year 1955, :month "April", :day 18}}
Out[42]:
nil

A more structured way to program - functions

In [43]:
(defn fix-date-of-birth
    [date-of-birth]
    (assoc date-of-birth
           :year (- (:year date-of-birth) 100)
           :day 14))

(pprint
 (update person :date-of-birth fix-date-of-birth))
{:name "Albert Einstein",
 :profession "Physicist",
 :date-of-birth {:year 1879, :month "March", :day 14},
 :date-of-death {:year 1955, :month "April", :day 18}}
Out[43]:
nil

Clojure idiomatic way

In [44]:
(-> person
    (update-in [:date-of-birth :year] #(- % 100))
    (assoc-in [:date-of-birth :day] 14)
    (pprint))
{:name "Albert Einstein",
 :profession "Physicist",
 :date-of-birth {:year 1879, :month "March", :day 14},
 :date-of-death {:year 1955, :month "April", :day 18}}
Out[44]:
nil

Note

The above example uses a feature called threading in Clojure. Threading will be discussed in the section of macros.