Data Structures
John Gabriele
2014-03
(list 1 2 3) ;=> (1 2 3)
(seq [1 2 3]) ;=> (1 2 3)
(vector 1 2 3) ;=> [1 2 3]
(vec '(1 2 3)) ;=> [1 2 3]
(hash-map :a 1 :b 2) ;=> {:a 1 :b 2}
(hash-set :a :b :c) ;=> #{:a :b :c}
(set [:a :b :c]) ;=> #{:a :b :c}
;; converting between the various concrete types
(vec some-map) ;=> [[:a 1] [:b 2]]
(set some-map) ;=> #{[:a 1] [:b 2]}
(apply hash-map [:a 1 :b 2]) ;=> {:a 1 :b 2}
(zipmap [:a :b] [1 2]) ;=> {:a 1 :b 2}
(interleave [:a :b :c] [1 2 3]) ;=> (:a 1 :b 2 :c 3)
(interleave [:a :b :c] [1 2 3] [:X :Y :Z])
;;=> (:a 1 :X :b 2 :Y :c 3 :Z)
(interpose "-" [1 2 3]) ;=> (1 "-" 2 "-" 3)
(map vector [1 2 3] [:a :b :c] '(x y z))
;;=> ([1 :a x] [2 :b y] [3 :c z])
;; You can use strings any place a seq is expected,
;; or else explicitly take a seq of one:
(seq "hello") ;=> '(\h \e \l \l \o)
1 Adding and Removing Items
1.1 Adding an item
(conj some-vec x) ; appends to some-vec
(conj some-list x) ; prepends to some-list
(conj some-set x)
(conj some-map [k v])
(assoc some-map k v)
1.2 Removing an item
;; For sequential collections, if you know the index
;; of the item you want to remove:
(concat (take idx coll)
(drop (inc idx) coll))
If you know the item you’d like to remove (and you don’t know its index):
(def stuff ["abc" "mno" "xyz" "mno" "pdq"])
(def this-one "xyz")
;; If you like, you can *find* the index of `this-one`,
(.indexOf stuff this-one)
;; then remove it as shown previously.
;; Remove all ocurrences of this-one:
(remove #(= % "mno")
stuff)
;;=> ("abc" "xyz" "pdq")
;; Remove one of the ocurrences of this-one:
(defn remove-one
[x coll]
(let [f (first coll)
r (rest coll)]
(if (= f x)
r
(cons f (remove-one x r)))))
(remove-one "mno" stuff) ;=> ("abc" "xyz" "mno" "pdq")
Interesting note pointed out to me by amalloy: functions which operate primarily on sequences should take the data structure argument last; just like how
map
,filter
,remove
,reduce
, etc. do.
To remove a more complex data structure from a sequential collection:
(def things [{:name "abc" :num 4}
{:name "mno" :num 7}
{:name "xyz" :num 9}
{:name "mno" :num 3}])
;; If you didn't know what order the items are in,
;; to remove any maps with :name "mno":
(remove #(= (:name x) "mno")
things)
;; If you just want to remove *one* with the name "mno",
;; it's almost the same as shown previously:
(defn remove-one-by-name
[its-name coll]
(let [f (first coll)
r (rest coll)]
(if (= (:name f) its-name)
r
(cons f (remove-one-by-name its-name r)))))
(remove-one-by-name "mno" things)
;;=> ({:name "abc" :num 4}
;; {:name "xyz" :num 9}
;; {:name "mno" :num 3})
Extra Credit: write your own generic
remove-one
where it takes a predicate as the the first arg!
For maps, use dissoc
to remove items.
For sets, use disj
.
2 Building up vector values in a hashmap
Although you’ll usually use group-by
for tasks like this, the following can nevertheless be handy:
(update-in {}
[:a]
(fnil conj []) ; start us off with an empty vec if necessary
"foo")
;;=> {:a ["foo"]}
(def input [[:a "foo"] [:b "bar"] [:a "baz"]])
;; Maybe useful in a `reduce`:
(reduce (fn [accum [k v]]
(update-in accum [k] (fnil conj []) v))
{}
input)
;;=> {:b ["bar"], :a ["foo" "baz"]}
;; Whereas...
(let [stuff (group-by first input)]
;; So, stuff is `{:a [[:a "foo"] [:a "baz"]], :b [[:b "bar"]]}`
(for [[k v] stuff]
[k (map second v)]))
;;=> ([:a ("foo" "baz")] [:b ("bar")])
Would have gotten
{:a ("foo")}
if we’d usedconj
instead of(fnil conj [])
.
3 Finding Items
;; Given an index:
(nth some-sequential idx)
;; Given the item, you can find the index:
(.indexOf some-sequential x) ; nil if x isn't in there.
;; See if a set contains an item:
(the-set x) ; nil if x isn't in the-set.
;; or, if `x` is a keyword:
(:some-kwd the-set)
;; See if a map has an entry for key `:k`:
(the-map :k)
(get the-map :k) ; same
(:k the-map)
Given a sequential collection of maps (an example of one of them might look like {:name "Foo" :num 4}
), find the one with some particular :name
:
(filter #(= (:name %) "Bar")
[{:name "Foo" :num 4}
{:name "Bar" :num 7}
{:name "Baz" :num 8}])
;; That works if the little maps are in any sort of container.
;; Note though: that gets *all* of the items where "Bar" is the
;; name. If you only want to get *one* where "Bar" is the name, wrap
;; the `filter` with `first`.
;; If you want to find the index of the first one
;; with that name:
(def stuff [{:name "Foo" :num 4}
{:name "Bar" :num 7}
{:name "Baz" :num 8}
{:name "Bar" :num 9}])
;; Well, here's one way that comes to mind.
(defn find-idx-of-one-by-name
[coll its-name]
(.indexOf (map #(= (:name %) its-name)
coll)
true))
4 Detecting Duplicates
Since you can use frequencies
to show you how many of each element there is in a collection, just filter out the results which only appear once:
(defn dups
"Like `frequencies` but only show entries with a val > 1."
[coll]
(into {} (filter (fn [[k v]] (> v 1))
(frequencies coll))))