Using Clojure update-in with multiple keys - dictionary

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))

Related

Clojure - walk with path

I am looking for a function similar to those in clojure.walk that have an inner function that takes as argument :
not a key and a value, as is the case with the clojure.walk/walk function
but the vector of keys necessary to access a value from the top-level data structure.
recursively traverses all data
Example :
;; not good since it takes `[k v]` as argument instead of `[path v]`, and is not recursive.
user=> (clojure.walk/walk (fn [[k v]] [k (* 10 v)]) identity {:a 1 :b {:c 2}})
;; {:a 10, :c 30, :b 20}
;; it should receive as arguments instead :
[[:a] 1]
[[:b :c] 2]
Note:
It should work with arrays too, using the keys 0, 1, 2... (just like in get-in).
I don't really care about the outer parameter, if that allows to simplify the code.
Currently learning clojure, I tried this as an exercise.
I however found it quite tricky to implement it directly as a walk down the tree that applies the inner function as it goes.
To achieve the result you are looking for, I split the task in 2:
First transform the nested structure into a dictionary with the path as key, and the value,
Then map the inner function over, or reduce with the outer function.
My implementation:
;; Helper function to have vector's indexes work like for get-in
(defn- to-indexed-seqs [coll]
(if (map? coll)
coll
(map vector (range) coll)))
;; Flattening the tree to a dict of (path, value) pairs that I can map over
;; user> (flatten-path [] {:a {:k1 1 :k2 2} :b [1 2 3]})
;; {[:a :k1] 1, [:a :k2] 2, [:b 0] 1, [:b 1] 2, [:b 2] 3}
(defn- flatten-path [path step]
(if (coll? step)
(->> step
to-indexed-seqs
(map (fn [[k v]] (flatten-path (conj path k) v)))
(into {}))
[path step]))
;; Some final glue
(defn path-walk [f coll]
(->> coll
(flatten-path [])
(map #(apply f %))))
;; user> (println (clojure.string/join "\n" (path-walk #(str %1 " - " %2) {:a {:k1 1 :k2 2} :b [1 2 3]})))
;; [:a :k1] - 1
;; [:a :k2] - 2
;; [:b 0] - 1
;; [:b 1] - 2
;; [:b 2] - 3
It turns out that Stuart Halloway published a gist that could be of some use (it uses a protocol, which makes it extensible as well) :
(ns user)
(def app
"Intenal Helper"
(fnil conj []))
(defprotocol PathSeq
(path-seq* [form path] "Helper for path-seq"))
(extend-protocol PathSeq
java.util.List
(path-seq*
[form path]
(->> (map-indexed
(fn [idx item]
(path-seq* item (app path idx)))
form)
(mapcat identity)))
java.util.Map
(path-seq*
[form path]
(->> (map
(fn [[k v]]
(path-seq* v (app path k)))
form)
(mapcat identity)))
java.util.Set
(path-seq*
[form path]
(->> (map
(fn [v]
(path-seq* v (app path v)))
form)
(mapcat identity)))
java.lang.Object
(path-seq* [form path] [[form path]])
nil
(path-seq* [_ path] [[nil path]]))
(defn path-seq
"Returns a sequence of paths into a form, and the elements found at
those paths. Each item in the sequence is a map with :path
and :form keys. Paths are built based on collection type: lists
by position, maps by key, and sets by value, e.g.
(path-seq [:a [:b :c] {:d :e} #{:f}])
({:path [0], :form :a}
{:path [1 0], :form :b}
{:path [1 1], :form :c}
{:path [2 :d], :form :e}
{:path [3 :f], :form :f})
"
[form]
(map
#(let [[form path] %]
{:path path :form form})
(path-seq* form nil)))
(comment
(path-seq [:a [:b :c] {:d :e} #{:f}])
;; finding nils hiding in data structures:
(->> (path-seq [:a [:b nil] {:d :e} #{:f}])
(filter (comp nil? :form)))
;; finding a nil hiding in a Datomic transaction
(->> (path-seq {:db/id 100
:friends [{:firstName "John"}
{:firstName nil}]})
(filter (comp nil? :form)))
)
Note : in my case I could also have used Specter, so if you are reading this, you may want to check it out as well.
There is also https://github.com/levand/contextual/
(def node (:b (first (root :a))))
(= node {:c 1}) ;; => true
(c/context node) ;; => [:a 0 :b]

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)

Clojure: idiomatic update a map's value IF the key exists

Here's my problem: I want a function helpme that takes a map and replaces the keys :r and :g with empty vectors if and only if those keys exist. For example:
Input:
(helpme {:a "1" :r ["1" "2" "3"] :g ["4" "5"]})
Output:
{:a "1" :r [] :g []}
Input:
(helpme {:a "1" :r ["1" "2" "3"]})
Output:
{:a "1" :r []}
I can define a function "helpme" that does this, but it's overly complicated, and I feel like there must be an easier (more idiomatic) way...
Here's the overly complicated way I've done, as requested below:
(defn c [new-doc k] (if (contains? new-doc k) (assoc new-doc k []) new-doc))
(defn helpme [new-doc] (c (c new-doc :r) :g))
(defn helpme [m]
(into m (for [[k _] (select-keys m [:r :g])]
[k []])))
Short, and only requires editing in one place when the number of items to set to [] changes.
In my search for a version of update-in which only updated the map if the key actually existed, Google insisted that I could find my answer here. For others in search of the same thing I've created the following helper functions:
(defn contains-in?
[m ks]
(not= ::absent (get-in m ks ::absent)))
(defn update-if-contains
[m ks f & args]
(if (contains-in? m ks)
(apply (partial update-in m ks f) args)
m))
That way you could:
> (def my-data {:a {:aa "aaa"}})
> (update-if-contains my-data [:a :aa] clojure.string/upper-case)
{:a {:aa "AAA"}}
> (update-if-contains my-data [:a :aa] clojure.string/split #" ")
{:a {:aa ["a" "aa"]}}
> (update-if-contains my-data [:a :b] clojure.string/upper-case)
{:a {:aa "aaa"}} ; no change because [:a :b] didn't exist in the map
(defn helpme
[mp]
(as-> mp m
(or (and (contains? m :r) (assoc m :r []))
m)
(or (and (contains? m :g) (assoc m :g []))
m)
m))
if there were a third replacement, I would use this function:
(defn replace-contained [m k v] (or (and (contains? m k) (assoc m k v)) m))
as-> is new in clojure 1.5 but the definition is very simple if you are stuck using an older clojure version:
(defmacro as->
"Binds name to expr, evaluates the first form in the lexical context
of that binding, then binds name to that result, repeating for each
successive form, returning the result of the last form."
{:added "1.5"}
[expr name & forms]
`(let [~name ~expr
~#(interleave (repeat name) forms)]
~name))
what about using cond-> for that purpose?
(defn helpme [m]
(cond-> m
(:r m) (assoc :r [])
(:g m) (assoc :g [])))
One option:
(defn helpme [m]
(merge m
(apply hash-map (interleave
(clojure.set/intersection
(set (keys m)) #{:r :g})
(repeat [])))))
If this is really as simple as conditionally setting the value of two fixed keys, I'd just write it out long hand to keep it simple.
(defn clean [m]
(let [m (if (:r m) (assoc m :r []) m)
m (if (:g m) (assoc m :g []) m)]
m))
If you want something more general and reusable, here's an option:
(defn cond-assoc [m & kvs]
(reduce
(fn [acc [k v]]
(if (get acc k)
(assoc acc k v)
acc))
m
(partition 2 kvs)))
(cond-assoc {:a "1" :r ["1" "2" "3"] :g ["4" "5"]}
:r []
:g []) ; {:r [] :a "1" :g []}
(cond-assoc {:a "1" :r ["1" "2" "3"]}
:r []
:g []) ; {:r [] :a "1"}
By testing for the expected key in the compare function
(sort-by #(if (number? (:priority %))
(:priority %)
(java.lang.Integer/MAX_VALUE))
<
[{:priority 100} {:priority 10} {:test 1}])
>({:priority 10} {:priority 100} {:test 1})
I really like the helpme API to include the keys to reset and the default value to reset to:
(defn helpme [m & {:keys [ks v]
:or {ks #{:r :g}
v []}}]
(apply assoc m (-> m
;; select only existing keys by selecting from original map
(select-keys ks)
keys
;; generate defaults for each (handled by applying `assoc`)
(interleave (repeat v)))))
This uses assocs variadic form by production the arguments to it.
If you give up the general API it can be as short as:
(defn helpme [m]
(apply assoc m (-> m
(select-keys #{:r :g})
keys
(interleave (repeat [])))))

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