Clojure: Trying to convert a hash-map into a string - functional-programming

I am pretty new to Clojure and still trying to grasp the fundamentals. While experimenting, I had the following problem. I tried to make a nested map and convert this into a JSON Object without the use of an external library, but keep running into a wall. I was wondering if someone could guide / help me to get on the right track.
I want to thank you in advance for taking the time to read this and help me out.
I have the following hash-map in Clojure:
; Creating Hashmap
(def hashmap
[
{ :Mineral-1 {
:Mineral-name {:value "Gold"}
:Color {:value "Golden"}
:Weight {:value 2324.23}
:Count {:value 203}
:Price {:value 20320.49}
:DeliveryContainers {:value-1 {:value 1}
:value-2 {:value 2}
:value-3 {:value 3}}
:Sold {:value true}
}
}
{ :Mineral-2 {
:Mineral-name {:value "Silver"}
:Color {:value "Silvered"}
:Weight {:value 2313.23}
:Count {:value 425}
:Price {:value 12345.12}
:DeliveryContainers {:value-1 {:value 4}
:value-2 {:value 5}
:value-3 {:value 6}}
:Sold {:value false}
}
}
]
)
And I am trying to convert it to the following string:
"
[{
"Minerals": [{
"Mineral-1": {
"Mineral-name": "Gold",
"Color": "Golden",
"Weight": 2324.23,
"Count": 203,
"Price": 20320.49,
"DeliveryContainers": [1, 2, 3]
},
"Mineral-2": {
"Mineral-name": "Silver",
"Color": "Silver",
"Weight": 2342.56,
"Count": 234,
"Price": 23123.23,
"DeliveryContainers": [4, 5, 6]
}
}]
}]"

Assuming you restrict yourself to a limited subset of Clojure structures, here is an example that you can use (if you excuse the poor formatting).
user> (def hmap
[ { :Mineral-1 {
:Mineral-name {:value "Gold"}
:Color {:value "Golden"}
:Weight {:value 2324.23}
:Count {:value 203}
:Price {:value 20320.49}
:DeliveryContainers {:value-1 {:value 1}
:value-2 {:value 2}
:value-3 {:value 3}}
:Sold {:value true} } }
{ :Mineral-2 {
:Mineral-name {:value "Silver"}
:Color {:value "Silvered"}
:Weight {:value 2313.23}
:Count {:value 425}
:Price {:value 12345.12}
:DeliveryContainers {:value-1 {:value 4}
:value-2 {:value 5}
:value-3 {:value 6}}
:Sold {:value false} } } ])
#'user/hmap
user> (declare print-json) ; forward declaration
#'user/print-json
user> (defn print-json-map [x]
(print "{ ")
(run! (fn [[k v]]
(print (str "\"" (name k) "\""))
(print ": ")
(print-json v))
x)
(println "}"))
#'user/print-json-map
user> (defn print-json-vec [x]
(print "[ ")
(run! #(print-json %) x)
(println "]"))
#'user/print-json-vec
user> (defn print-json-prim [x]
(cond (string? x) (print (str "\"" x "\""))
(number? x) (print x)
(boolean? x) (print x)
:else (println "CANNOT HAPPEN")))
#'user/print-json-prim
user> (defn print-json [x]
(cond (map? x) (print-json-map x)
(vector? x) (print-json-vec x)
:else (print-json-prim x)))
#'user/print-json
user> (print-json hmap)
[ { "Mineral-1": { "Mineral-name": { "value": "Gold"}
"Color": { "value": "Golden"}
"Weight": { "value": 2324.23}
"Count": { "value": 203}
"Price": { "value": 20320.49}
"DeliveryContainers": { "value-1": { "value": 1}
"value-2": { "value": 2}
"value-3": { "value": 3}
}
"Sold": { "value": true}
}
}
{ "Mineral-2": { "Mineral-name": { "value": "Silver"}
"Color": { "value": "Silvered"}
"Weight": { "value": 2313.23}
"Count": { "value": 425}
"Price": { "value": 12345.12}
"DeliveryContainers": { "value-1": { "value": 4}
"value-2": { "value": 5}
"value-3": { "value": 6}
}
"Sold": { "value": false}
}
}
]
nil
user>

Since this is for educational purpose I made a solution using more of Clojure's build in language features:
(import '[clojure.lang Sequential Keyword Symbol])
(import '[java.util Map])
(defprotocol MyJSON
(to-json [this]))
(def ^:dynamic *indent* 0)
(defmacro indent [& body]
`(binding [*indent* (inc *indent*)] ~#body))
(def indent-size 2)
(defn indent-space []
(apply str (repeat (* indent-size *indent*) " ")))
(defn comma-sep
([values] (comma-sep values ", "))
([values delim]
(apply str (butlast (interleave values (repeat delim))))))
(extend-protocol MyJSON
Boolean
(to-json [this] (str this))
String
(to-json [this] this)
Number
(to-json [this] (str this))
nil
(to-json [_] "null")
Keyword
(to-json [this] (name this))
Symbol
(to-json [this] (name this))
Sequential
(to-json [this] (str "[" (comma-sep (map to-json this)) "]"))
Map
(to-json [this] (str "\n" (indent-space) "{\n"
(indent (comma-sep
(map (fn [[k v]] (str (indent-space) (to-json k) ": " (to-json v))) this)
",\n"))
"\n"
(indent-space) "}" )))
Regarding {:value "1"}, which transforms "1", to and {:value-1 "1" :value-2 "2"}, which transforms to ["1" "2"]:
I suggest to do this transformation first and finally convert the transformed data to json.

Related

How to parallel filter data by value's truthy in Clojure?

We have some data like this:
(def x {:title ["NAME" "CODE" "ORDER" "MIN" "MAX" "IMG"]
:show-in-list [true true true true false false]
:key [:name :code :order :min :max :image]
:input-type [:txt :txt :num :num :num :img]
:value [nil nil nil nil nil nil]
:required [true true false false false false]})
We want to filter these values by :required's bool value which results in:
{:title ["NAME" "CODE"],
:show-in-list [true true],
:key [:part_name :part_code],
:input-type [:txt :txt],
:value [nil nil],
:required [true true]}
You can filter each element with ':required' vector:
(let [x {:title ["NAME" "CODE" "ORDER" "MIN" "MAX" "IMG"]
:show-in-list [true false true true false false]
:key [:name :code :order :min :max :image]
:input-type [:txt :txt :num :num :num :img]
:value [nil nil nil nil nil nil]
:required [true true false false false false]}]
(->> x
(map (fn [[k v]]
;; value v (which is a vector) is filtered out by its :required filter
[k (->> (map #(if %1 %2 ::false) (:required x) v)
;; remove ::false element
(remove #(= ::false %)))]))
;; add the updated key-value pair
(into (empty x))))
returns...
{:title ("NAME" "CODE"),
:show-in-list (true false),
:key (:name :code),
:input-type (:txt :txt),
:value (nil nil),
:required (true true)}
i would advice slightly different approach:
since you already need to filter out data in this 'packed' data structure, i guess you would potentially need to perform some other operations on it, so won't it be better to unpack/decompose the data structure to separate maps?
it could look like this:
(defn decompose [data]
(let [ks (keys data)]
(apply map #(zipmap ks %&) (vals data))))
this one unpacks the data:
user> (decompose x)
({:title "NAME",
:show-in-list true,
:key :name,
:input-type :txt,
:value nil,
:required true}
{:title "CODE",
:show-in-list true,
:key :code,
:input-type :txt,
:value nil,
:required true}
;;....more maps
)
then the recompose:
(defn recompose [data]
(when (seq data)
(apply merge-with
conj
(zipmap (keys (first data)) (repeat []))
data)))
this one, in turn, packs back the unpacked data.
so now your task could be fulfilled by simply filtering decomposed data and recomposing it back:
(->> x
decompose
(filter :required)
recompose)
;;=> {:title ["NAME" "CODE"],
;; :show-in-list [true true],
;; :key [:name :code],
;; :input-type [:txt :txt],
;; :value [nil nil],
;; :required [true true]}
To me it looks more general, and (what is more important) more readable.
(defn bar [state [required value]]
(if required (conj state value) state))
(defn foo [required values]
(reduce bar [] (map vector required values)))
(zipmap (keys x) (map #(foo (:required x) %) (vals x)))
We want to filter these values by :required's bool value
Pair required and value, if required then add value to new collection

A vector of maps into a tree

I have a collection that has the following structure [{:a 0} {:b 1} {:c 1} {:d 2} {:e 3} {:f 2}]. Basically, it is a tree, where an element of the vector is a node. What the number signify is the parent-child relationship. So, the element {:a 0} is the master parent (has no parents), while {:b 1}, {:c 1} are childs of {:a 0}. Also, {:d 2} is a child of {:c 1}.
What I want is to construct a list or a vector (doesn't matter at this point) that has the following structure:
[{:a {:b nil :c {:d {:e nil} :f nil}}}].
How can this be achieved?
This should work:
(fn [xs]
(loop [[x & rs :as xs] xs
m {}
lvl {}]
(if (seq xs)
(let [[k l] (first x)
p (conj (lvl (dec l) []) k)]
(recur
rs
(assoc-in m p nil)
(assoc lvl l p)))
m)))
As #jas mentioned we can't rely on key-order of map, so here we use lvl map to keep last seen element's path per level.

Add key with default value to nested clojure hashmap

I tried to do Clojure but stuck with the nested hashmap.
I have a structure like this:
{:type "view"
children: [
{:type "view"
:id "123"}
{:type "view"
:children [
{:type "view"}]}]}
Now I want to add field :id to each hashmap with random string if not exist. To get something like this:
{:type "view"
:id "43434"
children: [
{:type "view"
:id "123"}
{:type "view"
:id "456"
:children [
{:type "view"
:id "5656"}]}]}
You can use clojure.walk/postwalk to do this:
(walk/postwalk
(fn [v]
(if (and (map? v) (nil? (:id v)))
(assoc v :id (str (rand-int 9999)))
v))
data)
=>
{:type "view"
:id "3086"
:children [{:type "view"
:id "123"}
{:type "view"
:id "8243"
:children [{:type "view" :id "3222"}]}]}
...where data is your input map. postwalk is traversing your nested maps, and associating an :id key (a random integer string) on every map that doesn't have one.
in addition to the walk variant there are some more:
the simplest solution would be the recursive update
(defn upd [{:keys [id children] :as data}]
(assoc data
:id (if id
id
(str (rand-int Integer/MAX_VALUE)))
:children (mapv upd children)))
#'user/upd
user> (upd data)
;;=> {:type "view",
;; :children [{:type "view", :id "123", :children []}
;; {:type "view",
;; :children [{:type "view", :id "35223257",
;; :children []}],
;; :id "551012526"}],
;; :id "1899442274"}
you can also use clojure's zippers for that:
(require '[clojure.zip :as z])
(loop [curr (z/zipper map? :children (fn [n ch] (assoc n :children (vec ch))) data)]
(if (z/end? curr)
(z/root curr)
(recur (z/next
(let [node (z/node curr)]
(if (:id node)
curr
(z/replace curr
(assoc node :id
(str "id-" (rand-int Integer/MAX_VALUE))))))))))
;;=> {:type "view",
;; :children [{:type "view", :id "123"}
;; {:type "view",
;; :children [{:type "view", :id "id-92807721"}],
;; :id "id-1357268462"}],
;; :id "id-803083206"}

Clojure apply transform function to values of a map

Let's say I have a map :
{:top 2.8421709430404007E-14, :left 0, :down 240, :right 400N}
How can I iterate over it to convert everything into integers ?
{:top 0, :left 0, :down 240, :right 400}
An obvious solution would be :
{:top (:top m), :left (:left m), :down (:down m), :right (:right m)}
but it feels very repetitive.
I feel a reducing function could do nicely here, but I'm not sure how.
Doing a map over the entries could be another way:
(into {} (map (fn [[k v]] [k (int v)]) m))
;; {:top 0, :left 0, :down 240, :right 400}
I had to use reduce-kv:
(reduce-kv #(assoc %1 %2 (int %3)) {} {:top 2.8421709430404007E-14, :left 0, :down 240, :right 400N})
;;=>{:down 240, :left 0, :right 400, :top 0}
Edit : algo.generic.fmap looks like it would work as well.
Edit bis :
Thanks to #Andre for mentioning that a map-vals function exists in both weavejester/medley or prismatic/plumbing.
From medley :
(defn map-vals
"Maps a function over the values of an associative collection."
[f coll]
(reduce-map (fn [xf] (fn [m k v] (xf m k (f v)))) coll))
From plumbing :
(defn map-vals
"Build map k -> (f v) for [k v] in map, preserving the initial type"
[f m]
(cond
(sorted? m)
(reduce-kv (fn [out-m k v] (assoc out-m k (f v))) (sorted-map) m)
(map? m)
(persistent! (reduce-kv (fn [out-m k v] (assoc! out-m k (f v))) (transient {}) m))
:else
(for-map [[k v] m] k (f v))))

From multiple maps and a list of keys TO list of keys and corresponding values from maps

Input:
List of keys: [ :name :address :work]
Map 1: { :name "A" :address "A Street" }
Map 2: { :work "Work Ave" }
Output:
([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
This is what I have at the moment:
(defn maps-iterate [v & ms]
(map (fn [k] (into [] [k #(map (k %) ms)])) v))
(println (maps-iterate [ :name :address :work ] { :name "A" :address "A Street"} { :work "Work Ave" }))
Which gives me:
([:name #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#4b14b82b>]
[:address #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#3d47358f>]
[:work #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#e0d5eb7>])
Try
(defn maps-iterate [v & ms]
(map (fn [k] (into [] [k (map #(k %) ms)])) v))
Or even better:
(defn maps-iterate [v & ms]
(map (fn [k] (cons k (map k ms))) v))
Note: if all keys are keywords, then you can use them as functions: (map k ms) instead of (map #(k %) ms). If they are not then you can't use them as functions. You need to write (map #(% k) ms)
How about this?
(for [k ks]
[k (map k [m1 m2])])
;;=> ([:name ("A" nil)] [:address ("A Street" nil)] [:work (nil "Work Ave")])
or, if you really want a flat vector in the results:
(for [k ks]
(apply vector k
(map k [m1 m2])))
;;=> ([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
user=> (def a { :name "A" :address "A Street" })
#'user/a
user=> (def b { :work "Work Ave" })
#'user/b
user=> (def c [ :name :address :work])
#'user/c
user=> (map #(vector %1 (%1 a) (%1 b)) c)
([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
The following REPL example produces your output without the nil values indicated:
user> (def map1 { :name "A" :address "A Street" })
#'user/map1
user> (def map2 { :work "Work Ave" })
#'user/map2
user> (seq (merge map1 map2))
([:work "Work Ave"] [:name "A"] [:address "A Street"])

Resources