In Clojure how could I create an "add id to map" function? - dictionary

Say I have a collection of maps:
(def coll #{{:name "foo"} {:name "bar"}})
I want a function that will add an id (a unique number is fine) to each map element in the collection. i.e.
#{{:id 1 :name "foo"} {:id 2 :name "bar"}}
The following DOES NOT WORK, but it's the line of thinking I currently have.
(defn add-unique-id [coll]
(map assoc :id (iterate inc 0) coll))
Thanks in advance...

If you want to be really, really sure the IDs are unique, use UUIDs.
(defn add-id [coll]
(map #(assoc % :id (str (java.util.UUID/randomUUID))) coll))

How about
(defn add-unique-id [coll]
(map #(assoc %1 :id %2) coll (range (count coll))))
Or
(defn add-unique-id [coll]
(map #(assoc %1 :id %2) coll (iterate inc 0)))

Related

depth first tree traversal accumulation in clojure

I'd like to take a tree-like structure like this:
{"foo" {"bar" "1" "baz" "2"}}
and recursively traverse while remembering the path from the root in order to produce something like this:
["foo/bar/1", "foo/baz/2"]
Any suggestions on how this can be done without zippers or clojure.walk?
As nberger does, we separate enumerating the paths from presenting them as strings.
Enumeration
The function
(defn paths [x]
(if (map? x)
(mapcat (fn [[k v]] (map #(cons k %) (paths v))) x)
[[x]]))
... returns the sequence of path-sequences of a nested map. For example,
(paths {"foo" {"bar" "1", "baz" "2"}})
;(("foo" "bar" "1") ("foo" "baz" "2"))
Presentation
The function
#(clojure.string/join \/ %)
... joins strings together with "/"s. For example,
(#(clojure.string/join \/ %) (list "foo" "bar" "1"))
;"foo/bar/1"
Compose these to get the function you want:
(def traverse (comp (partial map #(clojure.string/join \/ %)) paths))
... or simply
(defn traverse [x]
(->> x
paths
(map #(clojure.string/join \/ %))))
For example,
(traverse {"foo" {"bar" "1", "baz" "2"}})
;("foo/bar/1" "foo/baz/2")
You could entwine these as a single function: clearer and more useful to
separate them, I think.
The enumeration is not lazy, so it will run out of
stack space on deeply enough nested maps.
This is my attempt using tree-seq clojure core function.
(def tree {"foo" {"bar" "1" "baz" "2"}})
(defn paths [t]
(let [cf (fn [[k v]]
(if (map? v)
(->> v
(map (fn [[kv vv]]
[(str k "/" kv) vv]))
(into {}))
(str k "/" v)))]
(->> t
(tree-seq map? #(map cf %))
(remove map?)
vec)))
(paths tree) ; => ["foo/bar/1" "foo/baz/2"]
Map keys are used to accumulate paths.
I did something real quick using accumulator, but it isn't depth first.
(defn paths [separator tree]
(let [finished? (fn [[_ v]] ((complement map?) v))]
(loop [finished-paths nil
path-trees (seq tree)]
(let [new-paths (mapcat
(fn [[path children]]
(map
(fn [[k v]]
(vector (str path separator k) v))
children))
path-trees)
finished (->> (filter finished? new-paths)
(map
(fn [[k v]]
(str k separator v)))
(concat finished-paths))
remaining-paths (remove finished? new-paths)]
(if (seq remaining-paths)
(recur finished remaining-paths)
finished)))))
In the repl
(clojure-scratch.core/paths "/" {"foo" {"bar" {"bosh" "1" "bash" "3"} "baz" "2"}})
=> ("foo/baz/2" "foo/bar/bash/3" "foo/bar/bosh/1")
The following uses recursive depth first traversal:
(defn combine [k coll]
(mapv #(str k "/" %) coll))
(defn f-map [m]
(into []
(flatten
(mapv (fn [[k v]]
(if (map? v)
(combine k (f-map v))
(str k "/" v)))
m))))
(f-map {"foo" {"bar" "1" "baz" "2"}})
=> ["foo/bar/1" "foo/baz/2"]
Here's my take:
(defn traverse [t]
(letfn [(traverse- [path t]
(when (seq t)
(let [[x & xs] (seq t)
[k v] x]
(lazy-cat
(if (map? v)
(traverse- (conj path k) v)
[[(conj path k) v]])
(traverse- path xs)))))]
(traverse- [] t)))
(traverse {"foo" {"bar" "1" "baz" "2"}})
;=> [[["foo" "bar"] "1"] [["foo" "baz"] "2"]]
Traverse returns a lazy seq of path-leaf pairs. You can then apply any transformation to each path-leaf, for example to the "/path/to/leaf" fullpath form:
(def ->full-path #(->> (apply conj %) (clojure.string/join "/")))
(->> (traverse {"foo" {"bar" "1" "baz" "2"}})
(map ->full-path))
;=> ("foo/bar/1" "foo/baz/2")
(->> (traverse {"foo" {"bar" {"buzz" 4 "fizz" "fuzz"} "baz" "2"} "faa" "fee"})
(map ->full-path))
;=> ("foo/bar/buzz/4" "foo/bar/fizz/fuzz" "foo/baz/2" "faa/fee")

Traversing a graph in clojure

Hope you are well. I am stuck with a recursive program that is suppose to traverse a graph until it finds a path back to the start node. the code is here,
(def graph {:A {:B 5 :D 5 :E 7}
:B {:C 4}
:C {:D 8 :E 2}
:D {:C 8 :E 6}
:E {:B 3}
})
(defn looper [g startnode & args]
(let [[inode] (vec args)
acc []]
(if (= startnode inode)
(conj acc inode)
(conj acc inode (map (partial looper g startnode) (vec (keys (get g inode)))))
)))
(looper graph :C)
there is something wrong with the way I accumulate the result I couldnt find what exactly.
The function should return something like '(CDC CEBC) for the above call.
This did the trick, hope its helpful for someone :)
(defn- dfs
[graph goal]
(fn search
[path visited]
(let [current (peek path)]
(if (= goal current)
[path]
(->> current graph keys
(remove visited)
(mapcat #(search (conj path %) (conj visited %))))))))
(defn findpath
"Returns a lazy sequence of all directed paths from start to goal
within graph."
[graph start goal]
((dfs graph goal) [start] #{start}))
(defn circular-path-count [graph node]
(flatten (map (fn [s]
(map count (findpath graph s node))) (vec (keys (get-in graph [node]))) )))
e.g. usage: (circular-path-count paths :B)

Using Clojure update-in with multiple keys

I'm trying to apply a function to all elements in a map that match a certain key.
(def mymap {:a "a" :b "b" :c "c"})
(update-in mymap [:a :b] #(str "X-" %))
I'm expecting
{:a "X-a", :c "c", :b "X-b"}
But I get
ClassCastException java.lang.String cannot be cast to clojure.lang.Associative clojure.lang.RT.assoc (RT.java:702)
Anyone can help me with this?
update-in is to update a single key in the map (at a particular nesting level, [:a :b] means update key :b inside the map value of key :a.
What you want can be done using reduce:
(reduce #(assoc %1 %2 (str "X-" (%1 %2)))
mymap
[:a :b])
Here's a generalized function:
(defn update-each
"Updates each keyword listed in ks on associative structure m using fn."
[m ks fn]
(reduce #(update-in %1 [%2] fn) m ks))
(update-each mymap [:a :b] #(str "X-" %))
In the solution below, the haspmap if first filtered, then it is mapped to the str function, and then merged with the original hashmap -
(def m {:a "a" :b "b" :c "c"})
(def keys #{:a :b})
(->> m
(filter (fn [[k v]] (k keys)))
(map (fn [[k v]] [k (str "X-" v)]))
(into {})
(merge m))

reverse lookup in a map

I have to extract a key from a map using a value. Is there a way to do this other than implementing reverse lookup myself?
I think that map-invert is the right way to do this.
From the docs:
;; Despite being in clojure.set, this has nothing to do with sets.
user=> (map-invert {:a 1, :b 2})
{2 :b, 1 :a}
;; If there are duplicate keys, one is chosen:
user=> (map-invert {:a 1, :b 1})
{1 :b}
;; I suspect it'd be unwise to depend on which key survives the clash.
You can reverse a map really easily with a 2-line function:
(defn reverse-map [m]
(into {} (map (fn [[a b]] [b a]) m)))
(def a {:a 1 :b 2 :c 3})
(reverse-map a)
=> {1 :a, 3 :c, 2 :b}
((reverse-map a) 1)
=> :a
Try
(some #(if (= (val %) your-val) (key %)) your-map)
If you are using ClojureScript or you need one more alternative :)
(zipmap (vals m) (keys m))
Another one:
(defn reverse-map [m]
(apply hash-map (mapcat reverse m)))
(defn reverse-lookup [m k]
(ffirst (filter (comp #{k} second) m)))
if you want to keep the keys, it is better to just invert the map, but collect the old keys in a set / list etc...
(defn map-inverse [m]
(reduce (fn [m' [k v]] (update m' v clojure.set/union #{k})) {} m))
(defn map-inverse [m]
(reduce (fn [m' [k v]] (update m' v conj k)) {} m))

Merge list of maps and combine values to sets in Clojure

What function can I put as FOO here to yield true at the end? I played with hash-set (only correct for first 2 values), conj, and concat but I know I'm not handling the single-element vs set condition properly with just any of those.
(defn mergeMatches [propertyMapList]
"Take a list of maps and merges them combining values into a set"
(reduce #(merge-with FOO %1 %2) {} propertyMapList))
(def in
(list
{:a 1}
{:a 2}
{:a 3}
{:b 4}
{:b 5}
{:b 6} ))
(def out
{ :a #{ 1 2 3}
:b #{ 4 5 6} })
; this should return true
(= (mergeMatches in) out)
What is the most idiomatic way to handle this?
This'll do:
(let [set #(if (set? %) % #{%})]
#(clojure.set/union (set %) (set %2)))
Rewritten more directly for the example (Alex):
(defn to-set [s]
(if (set? s) s #{s}))
(defn set-union [s1 s2]
(clojure.set/union (to-set s1) (to-set s2)))
(defn mergeMatches [propertyMapList]
(reduce #(merge-with set-union %1 %2) {} propertyMapList))
I didn't write this but it was contributed by #amitrathore on Twitter:
(defn kv [bag [k v]]
(update-in bag [k] conj v))
(defn mergeMatches [propertyMapList]
(reduce #(reduce kv %1 %2) {} propertyMapList))
I wouldn't use merge-with for this,
(defn fnil [f not-found]
(fn [x y] (f (if (nil? x) not-found x) y)))
(defn conj-in [m map-entry]
(update-in m [(key map-entry)] (fnil conj #{}) (val map-entry)))
(defn merge-matches [property-map-list]
(reduce conj-in {} (apply concat property-map-list)))
user=> (merge-matches in)
{:b #{4 5 6}, :a #{1 2 3}}
fnil will be part of core soon so you can ignore the implementation... but it just creates a version of another function that can handle nil arguments. In this case conj will substitute #{} for nil.
So the reduction conjoining to a set for every key/value in the list of maps supplied.
Another solution contributed by #wmacgyver on Twitter based on multimaps:
(defn add
"Adds key-value pairs the multimap."
([mm k v]
(assoc mm k (conj (get mm k #{}) v)))
([mm k v & kvs]
(apply add (add mm k v) kvs)))
(defn mm-merge
"Merges the multimaps, taking the union of values."
[& mms]
(apply (partial merge-with union) mms))
(defn mergeMatches [property-map-list]
(reduce mm-merge (map #(add {} (key (first %)) (val (first %))) property-map-list)))
This seems to work:
(defn FOO [v1 v2]
(if (set? v1)
(apply hash-set v2 v1)
(hash-set v1 v2)))
Not super pretty but it works.
(defn mergeMatches [propertyMapList]
(for [k (set (for [pp propertyMapList] (key (first pp))))]
{k (set (remove nil? (for [pp propertyMapList] (k pp))))}))

Resources