Collections: Maps
Overview
From https://clojure.org/reference/data_structures#Maps
A Map is a collection that maps keys to values. Two different map types are provided - hashed and sorted. Hash maps require keys that correctly support hashCode and equals. Sorted maps require keys that implement Comparable, or an instance of Comparator. Hash maps provide faster access (log32N hops) vs (logN hops), but sorted maps are, well, sorted. count is O(1). conj expects another (possibly single entry) map as the item, and returns a new map which is the old map plus the entries from the new, which may overwrite the old entries. conj also accepts a MapEntry or a vector of two items (key and value). seq returns a sequence of map entries, which are key/value pairs. Sorted map also supports rseq, which returns the entries in reverse order. Maps implement IFn, for invoke() of one argument (a key) with an optional second argument (a default value), i.e. maps are functions of their keys. nil keys and values are ok.
Maps Creation
Code
(ns collections-maps.core)
;; ---
;; Maps creation
;; ---
{:a 1, :b 2, :c 3, :d 4, :e 5}
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
{:a 1, :b "b", :c 3, :d "d"}
;; => {:a 1, :b "b", :c 3, :d "d"}
{:a 1, "b" "c", 2 "b", :d "d"}
;; => {:a 1, "b" "c", 2 "b", :d "d"}
(class {:a 1 :b 2 :c 3 :d 4 :e 5})
;; => clojure.lang.PersistentArrayMap
(hash-map :a 1 :b 2 :c 3 :d 4 :e 5)
;; => {:e 5, :c 3, :b 2, :d 4, :a 1}
(into {} [[:a 1] [:b 2] [:c 3] [:d 4] [:e 5]])
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
(= {:a 1, :b 2, :c 3, :d 4, :e 5} (hash-map :a 1 :b 2 :c 3 :d 4 :e 5))
;; => true
(assoc {:a 1 :b 2 :c 3 :d 4} :e 5)
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
(assoc {:a 1 :b 2 :c 3 :d 4} :a 5)
;; => {:a 5, :b 2, :c 3, :d 4}
(assoc {} :a 1 :b 2 :c 3 :d 4 :e 5)
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
(dissoc {:a 1 :b 2 :c 3 :d 4 :e 5} :a)
;; => {:b 2, :c 3, :d 4, :e 5}
(dissoc {:a 1 :b 2 :c 3 :d 4 :e 5} :f)
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
(conj {:a 1, :b 2, :c 3, :d 4} [:e 5])
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}Explanation
Sure, here is a line-by-line explanation of the Clojure code provided:
(ns collections-maps.core)- This line defines a new namespace calledcollections-maps.core. Namespaces in Clojure are used to group related functions and data together.{:a 1, :b 2, :c 3, :d 4, :e 5}- This is a literal syntax for creating a Clojure map, a collection of key-value pairs. Here, keywords:a,:b,:c,:d, and:eare the keys and1,2,3,4,5are the corresponding values.{:a 1, :b "b", :c 3, :d "d"}- Another example of a map. This map contains keys of mixed types - keywords and numbers, and values of mixed types - numbers and strings.(class {:a 1 :b 2 :c 3 :d 4 :e 5})- Theclassfunction returns the class (type) of the given value. In this case, it’sclojure.lang.PersistentArrayMap, which is the type of Clojure’s default map implementation.(hash-map :a 1 :b 2 :c 3 :d 4 :e 5)- This is another way of creating a map using thehash-mapfunction. The order of the key-value pairs may not be preserved.(into {} [[:a 1] [:b 2] [:c 3] [:d 4] [:e 5]])- Theintofunction is used to insert values from a collection (second argument) into a map (first argument). Here, it constructs a map from a vector of vectors, each inner vector being a key-value pair.(= {:a 1, :b 2, :c 3, :d 4, :e 5} (hash-map :a 1 :b 2 :c 3 :d 4 :e 5))- This line compares two maps for equality. In Clojure, two maps are equal if they contain the same keys and associated values, regardless of the order.(assoc {:a 1 :b 2 :c 3 :d 4} :e 5)- Theassocfunction adds a new key-value pair to a map or updates an existing key’s value. Here, it adds:e 5to the map.(dissoc {:a 1 :b 2 :c 3 :d 4 :e 5} :a)- Thedissocfunction removes a key-value pair from a map. Here, it removes the key:aand its associated value.(conj {:a 1, :b 2, :c 3, :d 4} [:e 5])- Theconjfunction “conjoins” a value to a collection. In the context of maps, it expects the value to be a map entry (a two-element vector) which it adds to the map. In this case, it adds the key-value pair:e 5to the map.
Accessing Elements
Code
(ns collections-maps.core)
;; ---
;; Accessing elements
;; ---
(:a {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => 1
({:a 1, :b 2, :c 3, :d 4, :e 5} :a)
;; => 1
(get {:a 1, :b 2, :c 3, :d 4, :e 5} :a)
;; => 1
(get {} :a)
;; => nil
(get {:a 1, :b 2, :c 3, :d 4, :e 5} :f "Default Value")
;; => "Default Value"
(get-in {:person {:name "John" :age 30}} [:person])
;; => {:name "John", :age 30}
(get-in {:person {:name "John" :age 30}} [:person :name])
;; => "John"
(get-in {:person {:name "John" :age 30}} [:person :address])
;; => nil
(get-in {:person {:name "John" :age 30}} [:person :address] "Default Value")
;; => "Default Value"
(keys {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => (:a :b :c :d :e)
(vals {:a 1, :b 2, :c 3, :d 4, :e 5})Explanation
This code includes some examples of how to work with maps in Clojure, specifically how to access and manipulate them. Here’s an explanation for each piece of code:
(:a {:a 1, :b 2, :c 3, :d 4, :e 5})- This is using a keyword as a function to get the corresponding value from a map. In this case, it returns
1, the value associated with the:akeyword.
- This is using a keyword as a function to get the corresponding value from a map. In this case, it returns
({:a 1, :b 2, :c 3, :d 4, :e 5} :a)- This is equivalent to the first operation but using a map as a function to look up a key. It also returns
1.
- This is equivalent to the first operation but using a map as a function to look up a key. It also returns
(get {:a 1, :b 2, :c 3, :d 4, :e 5} :a)- This uses the
getfunction to retrieve the value associated with the:akeyword from the map. It also returns1.
- This uses the
(get {} :a)- Here,
getis used on an empty map with:aas a key, so it returnsnilas there’s no such key in the map.
- Here,
(get {:a 1, :b 2, :c 3, :d 4, :e 5} :f "Default Value")- This uses
getwith a default value. If the key isn’t found in the map, it returns the default value, in this case,"Default Value".
- This uses
(get-in {:person {:name "John" :age 30}} [:person])get-inis used to retrieve a nested map from the outer map. Here, it returns the map{:name "John", :age 30}.
(get-in {:person {:name "John" :age 30}} [:person :name])- This retrieves a nested value by following the specified key path. It returns
"John".
- This retrieves a nested value by following the specified key path. It returns
(get-in {:person {:name "John" :age 30}} [:person :address])- This attempts to retrieve a nested value that doesn’t exist, resulting in
nil.
- This attempts to retrieve a nested value that doesn’t exist, resulting in
(get-in {:person {:name "John" :age 30}} [:person :address] "Default Value")- This is similar to the previous example but provides a default value when the key path doesn’t exist. It returns
"Default Value".
- This is similar to the previous example but provides a default value when the key path doesn’t exist. It returns
(keys {:a 1, :b 2, :c 3, :d 4, :e 5})- This retrieves all keys from the map and returns them as a sequence:
(:a :b :c :d :e).
- This retrieves all keys from the map and returns them as a sequence:
(vals {:a 1, :b 2, :c 3, :d 4, :e 5})- This retrieves all values from the map and returns them as a sequence:
(1 2 3 4 5).
- This retrieves all values from the map and returns them as a sequence:
In general, this code demonstrates various ways to retrieve keys and values from maps in Clojure, both at the top level and nested within other maps, and also how to specify default values if a key isn’t found.
Manipulating Maps
Code
(ns collections-maps.core)
;; ---
;; Manipulating maps
;; ---
(def map-a {:a 1, :b 2, :c 3, :d 4, :e 5})
(def map-b {:f 6, :g 7, :h 8, :i 9, :j 10})
(merge map-a map-b)
;; => {:e 5, :g 7, :c 3, :j 10, :h 8, :b 2, :d 4, :f 6, :i 9, :a 1}
(merge map-a)
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
(merge map-a {})
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}Explanation
This Clojure code demonstrates how to manipulate maps (essentially dictionaries or key-value pairs), specifically how to merge two maps together. First, let’s break it down line by line:
(ns collections-maps.core): This line creates a new namespace named “collections-maps.core”. This is the equivalent of a package in languages like Java. It helps to organize your code and avoid naming conflicts.(def map-a {:a 1, :b 2, :c 3, :d 4, :e 5}): This line defines a map namedmap-awhich associates keywords (:a,:b,:c,:d,:e) with the integers 1 through 5.(def map-b {:f 6, :g 7, :h 8, :i 9, :j 10}): Similarly, this line defines a map namedmap-bwith different keywords and integers.(merge map-a map-b): Themergefunction takes any number of maps and returns a map that combines all of them. If there are duplicate keys, the value from the last map with that key is used. Here, it combinesmap-aandmap-binto a single map. The resulting map will contain all key-value pairs from bothmap-aandmap-b.(merge map-a): When only one map is passed to themergefunction, it returns that map. So, in this case, it just returnsmap-a.(merge map-a {}): Heremergeis called withmap-aand an empty map. Since there are no key-value pairs in the empty map, the result is justmap-a.
Predicates
Code
(ns collections-maps.core)
;; ---
;; Predicates
;; ---
(empty? {})
;; => true
(empty? {:a 1, :b 2})
;; => false
(contains? {:a 1, :b 2, :c 3, :d 4, :e 5} :a)
;; => true
(contains? {:a 1, :b 2, :c 3, :d 4, :e 5} :f)
;; => false
(some #(= (second %) 3) {:a 1, :b 2, :c 3, :d 4, :e 5}) ;; alternative for checking if a map contains a value
;; => true
(some #(= (second %) 6) {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => nilExplanation
This code demonstrates several core functions in Clojure for working with maps: key-value data structures. Let’s break down each part of this code:
(ns collections-maps.core): This line is defining a namespace. In Clojure, namespaces are used to group related code together. Thensmacro is used to declare the namespace that the code that follows will be a part of. This line declares that the following code is part of thecollections-maps.corenamespace.(empty? {}): This is calling theempty?function with an empty map as an argument.empty?is a predicate function that checks if a collection is empty or not. In this case, since the map is indeed empty, the function returnstrue.(empty? {:a 1, :b 2}): Similar to the above, this is calling theempty?function but this time with a non-empty map as an argument. The map contains two key-value pairs::ais mapped to1and:bis mapped to2. Since this map is not empty, the function returnsfalse.(contains? {:a 1, :b 2, :c 3, :d 4, :e 5} :a): This line is calling thecontains?function with a map and a key as arguments.contains?checks if the given map contains the specified key. In this case, the map does contain the key:a, so the function returnstrue.(contains? {:a 1, :b 2, :c 3, :d 4, :e 5} :f): Similar to the previous, this line is calling thecontains?function but with the key:f. The map does not contain this key, so the function returnsfalse.(some #(= (second %) 3) {:a 1, :b 2, :c 3, :d 4, :e 5}): This line is using thesomefunction in conjunction with an anonymous function#(= (second %) 3)to check if any value in the map equals3. Thesomefunction applies the given function to each element in the collection and returns the first non-nil result, ornilif there is none. The anonymous function checks if the second element of the input (which, when iterating over a map, is the value of the key-value pair) is equal to3. The map contains3as one of its values, so the function returnstrue.(some #(= (second %) 6) {:a 1, :b 2, :c 3, :d 4, :e 5}): Similar to the above, this line uses thesomefunction to check if any value in the map equals6. The map does not contain6as one of its values, so the function returnsnil.
Utility Functions
Code
(ns collections-maps.core)
;; ---
;; Utility functions
;; ---
(count {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => 5
(count {})
;; => 0Explanation
This Clojure code resides within the namespace collections-maps.core. In Clojure, a namespace (defined with ns) contains a collection of related functions, macros, and data. The code executes the function count twice, once for each given map.
(count {:a 1, :b 2, :c 3, :d 4, :e 5})counts the number of key-value pairs in the map. In Clojure, a map is an associative data structure of key-value pairs. Here, the map contains five key-value pairs::amaps to1,:bmaps to2,:cmaps to3,:dmaps to4, and:emaps to5. Therefore, the count function returns5.(count {})counts the number of key-value pairs in an empty map. As there are no key-value pairs in the empty map, the count function returns0.
In summary, this Clojure code demonstrates how to use the count function to determine the number of key-value pairs in a map. The results are returned as integers.
Conversion to Other Collections
Code
(ns collections-maps.core)
;; ---
;; Conversion to other collections
;; ---
(apply list {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => ([:a 1] [:b 2] [:c 3] [:d 4] [:e 5])
(apply vector {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => [[:a 1] [:b 2] [:c 3] [:d 4] [:e 5]]
(apply hash-map [:a 1 :b 2 :c 3 :d 4 :e 5])
;; => {:e 5, :c 3, :b 2, :d 4, :a 1}
(into {:a 1} [[:b 2] [:c 3] [:d 4] [:e 5]])
;; => {:a 1, :b 2, :c 3, :d 4, :e 5}
(seq {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => ([:a 1] [:b 2] [:c 3] [:d 4] [:e 5])
(apply str {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => "[:a 1][:b 2][:c 3][:d 4][:e 5]"
(str {:a 1, :b 2, :c 3, :d 4, :e 5})
;; => "{:a 1, :b 2, :c 3, :d 4, :e 5}"Explanation
Let’s go through this code snippet by snippet.
(apply list {:a 1, :b 2, :c 3, :d 4, :e 5})This code applies thelistfunction to a map. In Clojure, maps are collections of key-value pairs. Theapplyfunction spreads the key-value pairs of the map into a sequence of arguments for the function (listin this case). The result is a list of key-value pairs representing each as a list.(apply vector {:a 1, :b 2, :c 3, :d 4, :e 5})Similar to the first example, it usesvectorinstead oflist. The result is a vector of key-value pairs, where each pair is a vector itself.(apply hash-map [:a 1 :b 2 :c 3 :d 4 :e 5])This creates a hash map from a vector. Theapplyfunction spreads the vector elements into a sequence of arguments for thehash-mapfunction, which then combines them into key-value pairs.(into {:a 1} [[:b 2] [:c 3] [:d 4] [:e 5]])Theintofunction takes two collections and adds the elements of the second one to the first one. In this case, it’s adding the key-value pairs represented as vectors in the vector to the map.(seq {:a 1, :b 2, :c 3, :d 4, :e 5})This is using theseqfunction to create a sequence from a map. Likeapply listandapply vector, this generates a sequence of key-value pairs, each represented as a vector, but it doesn’t wrap them in an extra list or vector.(apply str {:a 1, :b 2, :c 3, :d 4, :e 5})This applies thestrfunction to a map. Thestrfunction converts its arguments to strings and concatenates them. In this case, the key-value pairs are each converted to a string and concatenated without separating them.(str {:a 1, :b 2, :c 3, :d 4, :e 5})This converts the entire map to a string. Unlikeapply str, it doesn’t convert each pair separately and then concatenates them; it converts the whole map to a single string, preserving the map’s syntax.