Recursively mutating values in a map of maps with Specter - recursion

complete Clojure newbie here. This is a simple question, but I cant seem to get it:
Given that I have a nested hasmap of unknown depth, how do I use Specter's transform() to mutate the values of the data structure? I imagine a recursive-path is required here, but I cant get it to work. a working example is what I'm after and unfortunately, there isn't one in the docs.
(there is an example for set-val on a recursive map, but I don't know how to transform that into a transform use-case)
EDIT: More details were requested, so here they are:
I'm interested in a transform form that can mutate all the values of a nested maps - of any depth). for example, this transform would be able to increment all the values in the following maps (and any other nested map):
{:a 1 :b {:c 2 :d {:e 3}}}
AND
{:a 1 :b {:c 2}}
AND
{:a 1}
the line of code I'm interested in might look sth like this:
(transform <missing selector here> inc data)

Using the linked example:
(def MAP-NODES
(recursive-path [] p
(if-path map?
(continue-then-stay MAP-VALS p))))
(transform [MAP-NODES MAP-VALS number?] inc data)

Be sure to always study the Clojure CheatSheet. For something this simple, it might be easiest to just use clojure.walk/postwalk
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.walk :as walk]))
(dotest
(let [data {:a 1 :b {:c 2 :d {:e 3}}}
result (walk/postwalk (fn [item]
(if (int? item)
(inc item)
item))
data)]
(is= result {:a 2, :b {:c 3, :d {:e 4}}})))
For more advanced problems, you may be interested in the function walk-with-parents, which allows you to inspect all items on the path from the root node through to the current node.

Related

Why should update be preferred over assoc?

In clojure, to edit the values of the keys of a map, there are 2 options available:
update
assoc
The only difference that I could find from the clojure documentation is that the update function does the following extra thing:
If the key does not exist, nil is passed as the old value.
Are there any specific use cases where in I should use update or assoc other than this?
Or in which cases I should prefer what (If I have missed these use cases).
They both state clear intent in your code when used properly:
assoc hints to the reader, that new kv-pairs are added (or
replacing the values for existing keys) and that they tend to not be
relying on the current values for that keys.
update hints, that one value for a key gets updated and the original
value (if present) maybe used for this calculation.
(update {} :a (fnil inc 0))
; → {:a 1}
(update {:a 41} :a (fnil inc 0))
; → {:a 42}
(assoc {:z 0} :a 42 :b 23}
; → {:z 0, :a 42, :b 23}
You an emulate update with assoc as follows
(assoc m :foo (f (:foo m)))
which looks clunky and is better off as:
(update m :foo f)
which returns a new map with f applied on the the value corresponding to the :foo key.

Apart from maps and vectors are there any other uses of get in Clojure?

The uses that I know so far are on vectors:
(get [1 2 3 4] 2) ; => 3
and on maps:
(get {:a "a" :b "B" :c "c"} :c) ; => "c"
From documentation it says:
clojure.core/get ([map key] [map key not-found])
Returns the value mapped to key, not-found or nil if key not present.
Apart from maps and vectors, a common use for get is on strings:
(get "Cthulhu" 2) ;; => \h
get also works on sets and native Java(Script) arrays. One possible use in ClojureScript and JavaScript inter-op:
(def js-array (-> (js/Array 10) ;Create and fill native JS array
(.fill "a")
(.map (fn [_ i] i))))
(get js-array 3) ; => 3
As another example, get works for looking up by item in a set:
(get #{:b :c :a} :c) ;;=> :c
Note that it does not work with a (sorted) set and index, for example:
(get (sorted-set :b :a :c) 1) ;; throws exception
Further, maps, vectors, and sets act as functions of their members, so you can often avoid using get altogether:
(#{:a :b :c} :b) ; => :b
({:a 1 :b 2 :c 3} :b) ; => 2
([:a :b :c] 1) ; => :b
The advantage of using get with them is that you can provide a default value:
(get {:a :b :c} :d) ; => nil
(get {:a :b :c} :d :not-found) ; => :not-found
See also #Thumbnail's answer to understand how get works under the hood.
Further to #ToniVanhanla's answer, for the JVM, the relevant Clojure interface is clojure.lang.ILookup.
Looking, as the Americans say, under the hood,
Clojure get translates into a call to clojure.lang.RT/get.
If possible, this casts to ILookup and calls the appropriate
valAt method.
If not, it calls ...RT/getFrom
... whose clauses deal explicitly with, in turn,
Java maps,
Clojure sets, and
Java strings and arrays.
If none of these fits, it returns nil.
There is no parent interface for Java arrays: they all descend directly from Object. They are detected by Java's Class/isArray
Somewhat surprisingly, Clojure get does not work on Java collections such as Vectors:
(java.util.Vector. (seq "Hello, world!"))
=> [\H \e \l \l \o \, \space \w \o \r \l \d \!]
But
(get (java.util.Vector. (seq "Hello, world!")) 4)
=> nil

How to remove element from mutable vector?

I know how to create mutable vector:
(defn create-vector []
(intern *ns* (symbol "my-vector" (ref []))
=>my-namespace/my-vector
I know how to add element to that vector:
(dosync (alter my-vector conj "test"))
=>["test"]
Now I have "test" string in my mutable vector. But how I can remove it? I tried to use lazy-sequence function remove
(dosync (alter my-vector remove "test"))
but it doesn't work. How can I remove element from mutable vector?
EDIT:
The collection doesn't really have to be vector. I found solution for set, but will wait if someone will suggest solution for vector.
your mistake is that you pass the "test" arg to remove function, while it really accepts a predicate. In your case this one wold work (unproperly though, making a list instead of a vector):
(dosync (alter my-vector #(remove #{"test"} %)))
to keep it a vector you would have to do this:
(dosync (alter my-vector #(vec (remove #{"test"} %))))
what makes me curious about your example:
why would you use this weird construction:
(defn create-vector []
(intern *ns* (symbol "my-vector" (ref []))))
instead of just (def my-vector (ref []))
why do you use ref at all? would you be using it inside some transaction? if not, i would propose you to move to an atom (since it is also thread safe)
user> (def my-atom (atom []))
#'user/my-atom
user> (swap! my-atom conj "hello")
["hello"]
user> (swap! my-atom #(remove #{"hello"} %))
()
As specified in my edit, collection doesn't have to be really vector. #leetwinski posted answer how to do it for vector, but if I would decide to use set I found other solution. So if we create set the same way as in question replacing [] to #{}:
(defn create-vector []
(intern *ns* (symbol "my-set" (ref #{}))
=>my-namespace/my-vector
and add element:
(dosync (alter my-set conj "test"))
=>#{"test"}
, we can remove it
(dosync (alter my-set disj item))
=>#{}

Clojure recursive function

As a Clojure newbie, I'm bothered with this small problem:
I would like to iterate through a sequence and execute a split, and then a str (concatenation) function over the sequence elements.
Here is my sequence:
(("2.660.784") ("2.944.552") ("44.858.797"))
What I want to get is something like this:
("2660784" "2944552" "44858797")
And this is my attempt of creating recursive solution for my problem:
(defn old
[squence]
(let [size (count squence)]
(loop [counter 1]
(if (<= counter size)
(apply str (clojure.string/split
(first (first squence))
#"\b\.\b"
))
(old (rest squence)))
)))
And of course, this is not a solution because it applies split and str only to one element, but I would like to repeat this for each element in squence. The squence is product of some other function in my project.
I'm definitely missing something so please help me out with this one...
The simplest way to write it is with replace, rather than split/str. And once you've written a function that can do this transformation on a single string, you can use map or for to do it to a sequence of strings. Here I had to destructure a bit, since for whatever reason each element of your sequence is itself another sequence; I just pulled out the first element.
(for [[s] '(("2.660.784") ("2.944.552") ("44.858.797"))]
(clojure.string/replace s #"\b\.\b" ""))
user=> (defn reject-char-from-string
[ch sequence]
(map #(apply str (replace {ch nil} (first %))) sequence))
#'user/reject-char-from-string
user=> (reject-char-from-string \. '(("2.660.784") ("2.944.552") ("44.858.797"))
)
("2660784" "2944552" "44858797")
Tried this?
=> (flatten '(("2.660.784") ("2.944.552") ("44.858.797")))
("2.660.784" "2.944.552" "44.858.797")
Is it as simple as this?
(def data '(("2.660.784") ("2.944.552") ("44.858.797")))
(require '[clojure.string :as string])
(map #(string/replace (first %1) "." "") data)
;=> ("2660784" "2944552" "44858797")

How to handle re-mapping of optional keys in function parameter

I have a Clojure function with parameter: [{:keys [from to]}]. Both the from and to keys are optional. I would like to remap these keys into new keys but maintaining the values. What's an efficient/idiomatic way to go about this? Here's my curent solution.
(defn query
[{:keys [from to]}]
(let [re-map {:$gte from
:$lt to}]
(into {} (remove #(nil? (val %)) re-map))))
I need it to return either one or both, and no nil if one of the key wasn't entered. So
=>(query {:from 10})
{:$gte 10}
and
=>(query {:from 10 :to 20})
{:$gte 10 :lt 20}
There is a function for this in the clojure.set namespace called rename-keys:
user=> (use 'clojure.set)
nil
user=> (rename-keys {:from 10} {:from :$gte :to :$lt})
{:$gte 10}
You can use destructuring for this:
user=> (defn re-map [{the-x :x the-y :y}] the-x)
user=> (re-map {:x 10 :y 1})
10
I would recommend this excellent introduction with lot of examples.

Resources