Jump to content

Clojure Programming/Examples/API Examples/Advanced Data Structures

From Wikibooks, open books for an open world

Lists

[edit | edit source]

Vectors

[edit | edit source]
 (get {:a 1, :b 2} :a) 
 ;; ⇒ 1

get also accepts an optional third argument which is returned if key is not found in map:

 (get {:a 1, :b 2} :e 0) 
 ;; ⇒ 0

 ;;maps are functions of their keys (and keys likewise), they delegate to get:
 ({:a 1, :b 2, :c 3} :a) 
 ;; ⇒ 1
 (:b {:a 1, :b 2} 99)
 ;; ⇒ 2

assoc-in

[edit | edit source]
 (def nested-structure { :level 0, 
         :nested1 { :level 1, 
           :nested2 { :level 2, 
             :final-data "initial data"}}})

 (assoc-in nested-structure [:nested1 :nested2 :final-data] "new data")
 ;; ⇒ {:level 0, :nested1 {:nested2 {:level 2, :final-data "new data"}, :level 1}}

merge

[edit | edit source]

Combine two maps into a more massive map

 (merge {:a 1} {:b 2})
 ;; ⇒ {:a 1, :b 2})
 (merge-with + {:a 1} {:a 2, :b 3})
 ;; ⇒ {:a 3 :b 3}

Using merge-with (ClojureDocs) you can specify what course of action to take for collisions, here we decide to join collisions into a set using union (sets in the Cookbook):

 ;; We have two maps with sets of normal users and malicious users
 ;; and we'll try merging them
 (use 'clojure.set)

 (def group1 {:normal #{"Alice" "Bob"} :malicious #{"Eve"}})
 (def group2 {:normal #{"Spock" "Alice"} :malicious #{"Sauron"}})

 ;; Naïve attempt

 (merge group1 group2)
 ;; ⇒ {:malicious #{"Sauron"}, :normal #{"Alice" "Spock"}}

 ;; Wait, where did "Eve" go? Where did "Bob" go? We'll have to use merge-with:

 (merge-with union group1 group2)
 ;; ⇒ {:malicious #{"Sauron" "Eve"}, :normal #{"Alice" "Bob" "Spock"}}

update-in

[edit | edit source]
 (def my-map {:a 1 :b 2})

 (update-in my-map [:a] #(- % 4))
 ;; ⇒ {:a -3, :b 2}

Struct Maps

[edit | edit source]

accessor

[edit | edit source]
 (defstruct employee :name :id)    
 (def e (struct employee "John" 123))
 e
 ;; ⇒ {:name "John", :id 123}

 ("name" e) ; FAIL: string not an accessor
 ;; ERROR ⇒ java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

 (:name e)                                                         
 ;; ⇒ "John"

 (def employee-name (accessor employee :name))  ; bind accessor to e-name
 (employee-name e) ; use accessor
 ;; ⇒ "John"

assoc

[edit | edit source]

See struct-map for more.

defstruct

[edit | edit source]

See struct-map for more.

struct

[edit | edit source]

See struct-map for more.

struct-map

[edit | edit source]
 (defstruct employee :name :id)
 (struct employee "Mr. X" 10)
 ;; ⇒ {:name "Mr. X", :id 10}
 (struct-map employee :id 20 :name "Mr. Y")
 ;; ⇒ {:name "Mr. Y", :id 20}
 (def a (struct-map employee :id 20 :name "Mr. Y"))
 (def b (struct employee "Mr. X" 10))

 ;; observe that :name and :id are accessors
 (:name a)     ; ⇒ "Mr. Y"
 (:id b)       ; ⇒ 10
 (b :id)       ; ⇒ 10
 (b :name)     ; ⇒ "Mr. X"

 (assoc a :name "New Name")
 ;; ⇒ {:name "New Name", :id 20}
 a                   ; note that 'a' is immutable and did not change
 ;; ⇒ {:name "Mr. Y", :id 20}
 (def a1 (assoc a :name "Another New Name")) ; bind to a1
  a1
 ;; ⇒ {:name "Another New Name", :id 20}

Array Maps

[edit | edit source]

union

[edit | edit source]
 #{1 2 2 3 \a 4 \a "test" "test1" "test"}
 ;; ⇒ #{1 \a 2 3 4 "test" "test1"}
 (clojure.set/union #{1 2 3} #{1 4 7})
 ;; ⇒ #{1 2 3 4 7}

index

[edit | edit source]
 (clojure.set/index [{:a 1 :b 2} {:a 1 :b 4}] [:a :b])
 ;; ⇒ {{:b 4, :a 1} #{{:a 1, :b 4}}, {:b 2, :a 1} #{{:a 1, :b 2}}}
 (clojure.set/index [{:a 1 :b 2} {:a 1 :b 4}] [:a])
 ;; ⇒ {{:a 1} #{{:a 1, :b 4} {:a 1, :b 2}}}

select

[edit | edit source]
 (clojure.set/select odd? #{1 2 3 4})
 ;; ⇒ #{1 3}

project

[edit | edit source]
 (clojure.set/project [{:a 1 :b 2 :c 3} {:a 4 :b 5 :c 6}] [:a :b])
 ;; ⇒ #{{:b 2, :a 1} {:b 5, :a 4}}
 (clojure.set/join #{{:a 1 :b 2} {:a 3 :b 4}} #{{:c 5 :d 6} {:c 1 :d 8}} {:a :c})
 ;; ⇒ #{{:d 8, :c 1, :a 1, :b 2}}

Zippers

[edit | edit source]

zipper

[edit | edit source]
 (-> (zip/vector-zip [[1 2] 3 [[4 5] 7 8]])
  zip/down
  zip/right
  zip/right
  zip/down
  zip/down
  zip/right
  (zip/edit inc)
  zip/root)
 ;; ⇒ [[1 2] 3 [[4 6] 7 8]]

A zipper is a way to modify a tree in a functional way- To do this conveniently, there needs to be a way for you to "drill down" a branch in the tree and perform a local edit, without having to tediously rebuild the upper areas of the tree again. In this example, the vector-zip function converts nested arrays into a zipper. Then we "drill down" the zipper with "down" and "right". Once we've reached the node we want to edit, we do so with "edit", in this case, incrementing the number. Then we can call "root" to convert the zipper back into nested arrays. Note that we were able to efficiently make a pinpoint change in the tree in a very elegant manner.

zipmap

[edit | edit source]
 (zipmap '(\a \b \c) '(1 2 3))
 ;; ⇒ {\c 3, \b 2, \a 1}
 (let [ks [1 3 4]] (zipmap ks (map inc ks)))
 ;; ⇒ {4 5, 3 4, 1 2}