{:check ["true"], :rank ["create" "destructuring" "transform"]}
{:name "Albert Einstein"
:profession "Physicist"
:date-of-birth "March 14, 1879"}
{"Toronto" '(3 :million)
"Oshawa" '(150 :thousand)}
(hash-map :name "Albert Einstein"
:profession "Physicist")
(zipmap [:name :profession] ["Albert Einstein" "Physicist"])
(require '[clojure.pprint :refer [pprint]])
(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}
]})
(keys person)
(pprint (vals person))
; Hashmaps are functions
; key -> value
(person :name)
; Many other ways of getting the value based on the index
(get person :name)
; Keywords are __functions__ of hashmap to value
; hashmap -> value
(:name person)
; nil signifies non-existing keys
(get person :last-name)
(person :last-name)
(:last-name person)
(contains? person :name)
(contains? person :last-name)
; nested destructuring using get
(get (get person :date-of-birth) :month)
; nested destructuring using keyword as function
(:month (:date-of-birth person))
; nested destructuring using hash-map as function
((person :date-of-birth) :month)
; get-in with key path
(get-in person [:date-of-birth :month])
; Title of first achievement
(:title (first (person :achievements)))
; Title of first achievement
; Here we use the fact that vectors are functions too.
(((person :achievements) 0) :title)
; get-in is a very useful function for deep navigation into
; nested data
(get-in person [:achievements 0 :title])
(require '[clojure.pprint :refer [pprint]])
;
; 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)
;
; assoc can be used to set a new value to an existing key
;
(assoc {:a 1 :b 2} :b 3)
;
; 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))
In the previous example, we see that x
remains the same, even after assoc.
So, assoc only transforms x
into y
.
;
; We can associate multiple key/value pairs in one `(assoc ...)`
;
(assoc {:a 1 :b 2}
:c 3
:d 4
:e 5)
;
; Dissociating a key from a hash-map
;
(dissoc {:a 1 :b 2} :a)
;
; Dissociating a non-existing key does nothing
;
(dissoc {:a 1 :b 2} :c)
Note: dissoc
, just like assoc
, does not change the input hash-map. It always produces a new hash-map.
update
function:
$$\mathrm{update} : \mathrm{hashmap}, k, f \to \mathrm{hashmap}$$;
; Updating the value of an existing key
;
(update {:a 1 :b 2} :b inc)
;
; Update :b by multiplication with 1000
;
(update {:a [1 2] :b 3} :b (fn [v] (* 1000 v)))
;
; Update :a by adding two strings to the vector
;
(update {:a [1 2] :b 3}
:a
(fn [xs] (into xs ["Hello" "World"])))
;
; 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)))))
;
; Merging multiple hashmaps into one
;
(merge {:a 1 :b 2} {:c 3 :b 4})
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\} $$(merge-with + {:a 1 :b 2} {:c 3 :b 4})
(merge-with list {:a 1 :b 2} {:c 3 :b 4})
(merge-with
(fn [x y] (format "%d <-> %d" x y))
{:a 1 :b 2}
{:c 3 :b 4})
(into {:a 1 :b 2}
[[:last-name "Albert"]
[:first-name "Einstein"]])
(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}})
(pprint
(update-in person [:date-of-birth :year] #(- % 100)))
(pprint
(assoc-in
(update-in person [:date-of-birth :year] #(- % 100))
[:date-of-birth :day]
14))
(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))
(-> person
(update-in [:date-of-birth :year] #(- % 100))
(assoc-in [:date-of-birth :day] 14)
(pprint))
Note
The above example uses a feature called threading in Clojure. Threading will be discussed in the section of macros.