Clojure: search/replace values in a dynamic nested map/seq - collections

I have a dynamically created map data structure which will later on be parsed into JSON. Therefore the nesting levels, etc. are unknown and depend on the data.
If a key has multiple values, they are represented as maps inside a sequence.
Here is an example:
{:key "value"
:anotherKey "anotherValue"
:foo "test"
:others [ {:foo "test2"} {:foo "test3"} ]
:deeper {:nesting {:foo "test4"} }
}
I now want to search for the key :foo and append "/bar" to the value.
The result should return the modified map:
{:key "value"
:anotherKey "anotherValue"
:foo "test/bar"
:others [ {:foo "test2/bar"} {:foo "test3/bar"} ]
:deeper {:nesting {:foo "test4/bar"} }
}
What would be a clean and simple way to achieve that?
I tried a recursive approach but beside the memory problem of large data structures I'm struggling with returning my appended values.

There might be something simpler than this:
(clojure.walk/prewalk
(fn [m]
(if (and (map? m) (:foo m))
(update-in m [:foo] #(str % "/bar"))
m))
{:key "value"
:anotherKey "anotherValue"
:foo "test"
:others [{:foo "test2"} {:foo "test3"}]
:deeper {:nesting {:foo "test4"}}})
=>
{:anotherKey "anotherValue",
:key "value",
:deeper {:nesting {:foo "test4/bar"}},
:foo "test/bar",
:others [{:foo "test2/bar"} {:foo "test3/bar"}]}

Related

Which approach to find multiple totals more closely follows the functional programming paradigm?

Say you have an array of objects with the structure like {id: 1, type: 'A', value: 10} want to find the total of type A, type B, and type C.
It would be more efficient to initialize the total variables and then loop through the array once, adding the the total variables based on type, than to use a reduce function for each total, in effect looping over the array 3 times.
However, from what I understand from the functional programming paradigm, functions should not manipulate anything outside of it internal scope and functions should have just one purpose, so the latter approach would be preferred.
Approach 1: initialize a variable for each of the three types, loop once and add to each total based on type
Approach 2: use reduce function for each total type.
Which one is preferred?
You can use a single fold/reduce if you use a record containing the three values as the state e.g. in clojure:
(defn sum-inputs [inputs]
(reduce (fn [acc {:keys [type value]}]
(update acc (keyword type) + value))
{:A 0 :B 0 :C 0}
inputs))
then
(sum-inputs [{:id 1 :type "A" :value 10}
{:id 2 :type "B" :value 12}
{:id 3 :type "B" :value 7}
{:id 4 :type "C" :value 40}])
in Javascript it looks like you can use Array.reduce:
const input = [{id: 1, type: "A", value: 4}, {id: 2, type: "B", value: 3}, {id: 3, type: "B", value: 9}, {id: 4, type: "C", value: 2}]
input.reduce(function(acc, i) { acc[i.type] += i.value; return acc; }, {A: 0, B: 0, C: 0})
note this mutates the accumulator record in place.

Clojure - filter nested map on innermost level

what would be the best way of filtering the following nested map, keeping the nested map structure. In my example Alice and Bob can be duplicated, e.g. the same employee can be working in several different plants at a time.
(def universe
{:customer1
{:plant1
{ "Alice" {:age 35 :sex "F"}
"Bob" {:age 25 :sex "M"}}
:plant2 {}}
:customer2 {}
})
I would like to for example filter by age >30 and return the same map structure. Ideally this would work for any nested map depth, filtering on the innermost level. Expected result:
(def universe
{:customer1
{:plant1
{ "Alice" {:age 35 :sex "F"}
}
:plant2 {}}
:customer2 {}
})
I've looked at clojure filter nested map to return keys based on inner map values but it doesn't look to be solving my problem. Thank you,
It is very similar to one of previous questions:
(use '[com.rpl.specter])
(let [input {:customer1
{:plant1
{"Alice" {:age 35 :sex "F"}
"Bob" {:age 25 :sex "M"}}
:plant2 {}}
:customer2 {}}
desired-output {:customer1
{:plant1 {"Alice" {:age 35 :sex "F"}}
:plant2 {}}
:customer2 {}}
RECUR-MAP (recursive-path [] p (cond-path map? (continue-then-stay [MAP-VALS p])))]
(clojure.test/is (= (setval [RECUR-MAP (pred :age) #(> 30 (:age %))] NONE input)
desired-output)))
Your data is a bit unusual in that one would normally expect :customer1, :customer2 etc to be different entries in a vector. For semi-structured data like this, I would consider postwalk:
(ns tst.demo.core
(:use tupelo.core demo.core tupelo.test)
(:require
[clojure.walk :as walk] ))
(def universe
{:customer1
{:plant1
{"Alice" {:age 35 :sex "F"}
"Bob" {:age 25 :sex "M"}}
:plant2 {}}
:customer2 {}})
(def age-of-wisdom 30)
(defn wisdom-only
[form]
(let [filter-entry? (when (map-entry? form)
(let [[-name- details] form
age (:age details)] ; => nil if missing
(and age ; ensure not nil
(< age age-of-wisdom))))]
(if filter-entry?
{}
form)))
(walk/postwalk wisdom-only universe) =>
{:customer1
{:plant1
{"Alice" {:age 35, :sex "F"}}
:plant2 {}}
:customer2 {}}
Thanks to #akond for the answer, reading the code made me think of a non specter solution. Still, slightly surprised there's no easy way to apply filter in this use case.
(defn recursive-filter [pred k m]
(letfn [(pair-filter [pair] (if (pred (k (second pair))) pair nil))]
(into {}
(for [a m]
(if (empty? (second a))
[(first a) {}]
(if (contains? (second a) k)
(pair-filter a)
[(first a) (recursive-filter pred k (second a))]))))))

creating a nested clojure map by calling function with its keys and values

I am stuck with a problem.
So the problem is.
I have a map like this {:first {"value1" "value2" "value3"...} :second {"value1" "value2" "value3"...}....}.
I have a function which
make a request to the server with parameters (first&value1) and return
some information (it must make request with each keys and their
values (first&value1, first&value2 ...second&value1...))
Next step is to generate new map like:
{:first
{:value1
{subvalue1, subvalue2 ..}
:value2
{subvalue2-1,subvalue2-2}}
:second
{:value3
{:subvalue3-1,:subvalue3-2}..}..}
Subvalues is the result of making a request with each key and each item of its value.
And i want to repeat the operation once again(with 3 parameters when I make a request to the server) to achieve 4 times nested map: {first {second {third {fourth}}}}.
Maybe somebody give me helpful advice how to do it.
This function is a bit long-winded but does what you need it to do:
(defn rec-update
[m f]
(let [g (fn g [m args]
(into {}
(for [[k v] m]
(if (instance? java.util.Map v)
[k (g v (conj args (name k)))]
[k (into {} (map #(let [args (into args [(name k) (name %)])]
[(keyword %) (f args)])
v))]))))]
(g m [])))
The f parameter should be a function that takes a collection of params, and returns a vector of results. Here is a sample that picks random numbers of random responses:
(defn make-request
[params]
(vec (repeatedly (+ 1 (rand-int 3)) #(rand-nth ["r1" "r2" "r3"]))))
Though the below example does not demonstrate, the params given to this function will indeed be the nested values up to that point.
To use:
(def m {:first ["val1" "val2" "val3"], :second ["val4" "val5"]})
(rec-update m make-request)
=>
{:first {:val1 ["r2" "r2" "r3"], :val2 ["r2" "r2"], :val3 ["r1" "r3"]},
:second {:val4 ["r3" "r3"], :val5 ["r2" "r1"]}}
Run it again on the result:
(rec-update *1 make-request)
=>
{:first {:val1 {:r2 ["r1" "r3" "r2"], :r3 ["r3" "r2"]},
:val2 {:r2 ["r1" "r1"]},
:val3 {:r1 ["r2"], :r3 ["r1" "r2" "r3"]}},
:second {:val4 {:r3 ["r3" "r2"]}, :val5 {:r2 ["r1"], :r1 ["r2" "r3"]}}}
As you can see, any duplicate values returned from the request will only be represented once in the result map.

Looking up values in a map with a vector of integer keys in Clojure

I have a map like this:
(def my-map {43423 43.3, 63452 32.02, 823828 67.43, ...})
and a vector of keys that are in a different order:
(def my-keys [63452 823828 43423 ...])
How can I call the my-keys vector on my-map to pull out the values and maintain the order of the vector, as below?
;=> [32.02 67.43 43.3 ...]
Any datatype returned is fine as long as the order is maintained.
Use map
(map my-map my-keys)
;=> (32.02 67.43 43.3)
This works because {} maps implement the function interface by looking up the argument in themselves.
({:foo 1 :bar 2} :bar)
;=> 2

Transform nested clojure maps

I am having trouble transforming a clojure map. The map has a vector as element and the vectors in turn have maps as elements.
The original map looks like this:
{"values" [{"sub" false, "name" "Adhoc"} {"acm" true, "list" true, "deval" true, "name" "Buyer"}]}
The maps within the vector always have the key "name" but the other keys may vary.
The name element should act as a key within the map.
As end result I need the original map to be transformed into this:
{"values" {"Adhoc" {"sub" false}, "Buyer" {"deval" true, "acm" true, "list" true}}
The problem is that the maps within the vector can have any amount of elements and I don't really know how to solve that with looping.
Any suggestions would be highly appreciated.
This would process the vector of maps for you:
(defn merge-by
[maps k]
(->> maps
(map (juxt #(get % k) #(dissoc % k)))
(into {})))
(merge-by [{"sub" false, "name" "Adhoc"}
{"acm" true, "list" true, "deval" true, "name" "Buyer"}]
"name")
;; => {"Adhoc" {"sub" false}, "Buyer" {"deval" true, "acm" true, "list" true}}
And this would process the outer map (if stored in my-map):
(update-in my-map ["values"] merge-by "name")

Resources