I would like to create a mermaid graph from nested map like this
{"a" {"b" {"c" nil
"d" nil}}
"e" {"c" nil
"d" {"h" {"i" nil
"j" nil}}}}
I think it should first convert nested map to this form. Then it should be easy.
[{:out-path "a" :out-name "a"
:in-path "a-b" :in-name "b"}
{:out-path "a-b" :out-name "b"
:in-path "a-b-c" :in-name "c"}
{:out-path "a-b" :out-name "b"
:in-path "a-b-d" :in-name "d"}
{:out-path "e" :out-name "e"
:in-path "e-f" :in-name "f"}
{:out-path "e" :out-name "e"
:in-path "e-c" :in-name "c"}
{:out-path "e" :out-name "e"
:in-path "e-d" :in-name "d"}
{:out-path "e-d" :out-name "d"
:in-path "e-d-h" :in-name "h"}
{:out-path "e-d-h" :out-name "h"
:in-path "e-d-h-i" :in-name "i"}
{:out-path "e-d-h" :out-name "h"
:in-path "e-d-h-j" :in-name "j"}]
EDIT:
This is what I have created. But I have absolutely no idea how to add path to result map.
(defn myfunc [m]
(loop [in m out []]
(let [[[k v] & ts] (seq in)]
(if (keyword? k)
(cond
(map? v)
(recur (concat v ts)
(reduce (fn [o k2]
(conj o {:out-name (name k)
:in-name (name k2)}))
out (keys v)))
(nil? v)
(recur (concat v ts) out))
out))))
as far as i can see by mermaid docs, to draw the graph it is enough to generate all the nodes in the form of "x-->y" pairs.
we could do that with some a simple recursive function (i believe there are not so many levels in a graph to worry about stack overflow):
(defn map->mermaid [items-map]
(if (seq items-map)
(mapcat (fn [[k v]] (concat
(map (partial str k "-->") (keys v))
(map->mermaid v)))
items-map)))
in repl:
user>
(map->mermaid {"a" {"b" {"c" nil
"d" nil}}
"e" {"c" nil
"d" {"h" {"i" nil
"j" nil}}}})
;; ("a-->b" "b-->c" "b-->d" "e-->c" "e-->d" "d-->h" "h-->i" "h-->j")
so now you just have to make a graph of it like this:
(defn create-graph [items-map]
(str "graph LR"
\newline
(clojure.string/join \newline (map->mermaid items-map))
\newline))
update
you could use the same strategy for the actual map transformation, just passing the current path to map->mermaid:
(defn make-result-node [path name child-name]
{:out-path path
:out-name name
:in-path (str path "-" child-name)
:in-name child-name})
(defn map->mermaid
([items-map] (map->mermaid "" items-map))
([path items-map]
(if (seq items-map)
(mapcat (fn [[k v]]
(let [new-path (if (seq path) (str path "-" k) k)]
(concat (map (partial make-result-node new-path k)
(keys v))
(map->mermaid new-path v))))
items-map))))
in repl:
user>
(map->mermaid {"a" {"b" {"c" nil
"d" nil}}
"e" {"c" nil
"d" {"h" {"i" nil
"j" nil}}}})
;; ({:out-path "a", :out-name "a", :in-path "a-b", :in-name "b"}
;; {:out-path "a-b", :out-name "b", :in-path "a-b-c", :in-name "c"}
;; {:out-path "a-b", :out-name "b", :in-path "a-b-d", :in-name "d"}
;; {:out-path "e", :out-name "e", :in-path "e-c", :in-name "c"}
;; {:out-path "e", :out-name "e", :in-path "e-d", :in-name "d"}
;; {:out-path "e-d", :out-name "d", :in-path "e-d-h", :in-name "h"}
;; {:out-path "e-d-h", :out-name "h", :in-path "e-d-h-i", :in-name "i"}
;; {:out-path "e-d-h", :out-name "h", :in-path "e-d-h-j", :in-name "j"})
Related
I've got this list of fields (that's Facebook's graph API fields list).
["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"]
I want to generate a map out of it. The convention is following, if after a key vector follows, then its an inner object for the key. Example vector could be represented as a map as:
{"a" "value"
"b" {"c" {"t" "value"} "d" "value"}
"e" {"f" "value"}
"g" "value"}
So I have this solution so far
(defn traverse
[data]
(mapcat (fn [[left right]]
(if (vector? right)
(let [traversed (traverse right)]
(mapv (partial into [left]) traversed))
[[right]]))
(partition 2 1 (into [nil] data))))
(defn facebook-fields->map
[fields default-value]
(->> fields
(traverse)
(reduce #(assoc-in %1 %2 nil) {})
(clojure.walk/postwalk #(or % default-value))))
(let [data ["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"]]
(facebook-fields->map data "value"))
#=> {"a" "value", "b" {"c" {"t" "value"}, "d" "value"}, "e" {"f" "value"}, "g" "value"}
But it is fat and difficult to follow. I am wondering if there is a more elegant solution.
Here's another way to do it using postwalk for the whole traversal, rather than using it only for default-value replacement:
(defn facebook-fields->map
[fields default-value]
(clojure.walk/postwalk
(fn [v] (if (coll? v)
(->> (partition-all 2 1 v)
(remove (comp coll? first))
(map (fn [[l r]] [l (if (coll? r) r default-value)]))
(into {}))
v))
fields))
(facebook-fields->map ["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"] "value")
=> {"a" "value",
"b" {"c" {"t" "value"}, "d" "value"},
"e" {"f" "value"},
"g" "value"}
Trying to read heavily nested code makes my head hurt. It is worse when the answer is something of a "force-fit" with postwalk, which does things in a sort of "inside out" manner. Also, using partition-all is a bit of a waste, since we need to discard any pairs with two non-vectors.
To me, the most natural solution is a simple top-down recursion. The only problem is that we don't know in advance if we need to remove one or two items from the head of the input sequence. Thus, we can't use a simple for loop or map.
So, just write it as a straightforward recursion, and use an if to determine whether we consume 1 or 2 items from the head of the list.
If the 2nd item is a value, we consume one item and add in
:dummy-value to make a map entry.
If the 2nd item is a vector, we recurse and use that
as the value in the map entry.
The code:
(ns tst.demo.core
(:require [clojure.walk :as walk] ))
(def data ["a" "b" ["c" ["t"] "d"] "e" ["f"] "g"])
(defn parse [data]
(loop [result {}
data data]
(if (empty? data)
(walk/keywordize-keys result)
(let [a (first data)
b (second data)]
(if (sequential? b)
(recur
(into result {a (parse b)})
(drop 2 data))
(recur
(into result {a :dummy-value})
(drop 1 data)))))))
with result:
(parse data) =>
{:a :dummy-value,
:b {:c {:t :dummy-value}, :d :dummy-value},
:e {:f :dummy-value},
:g :dummy-value}
I added keywordize-keys at then end just to make the result a little more "Clojurey".
Since you're asking for a cleaner solution as opposed to a solution, and because I thought it was a neat little problem, here's another one.
(defn facebook-fields->map [coll]
(into {}
(keep (fn [[x y]]
(when-not (vector? x)
(if (vector? y)
[x (facebook-fields->map y)]
[x "value"]))))
(partition-all 2 1 coll)))
How would I get the following:
[{:foo "a" :bar "b" :biz "c"}
{:foo "d" :bar "e" :biz "f"}
{:foo "h" :bar "i" :biz "j"}]
from
("a" "d" "h")
("b" "e" "i")
("c" "f" "j")
Thanks in advance!
You can transpose the input using map and zipmap to create the result maps:
(def input ['("a" "d" "h")
'("b" "e" "i")
'("c" "f" "j")])
(mapv #(zipmap [:foo :bar :biz] %) (apply map vector input))
this is very alike the #lee's variant, but does it in one pass, employing the clojure map's ability to operate on multiple collections:
(def input ['("a" "d" "h")
'("b" "e" "i")
'("c" "f" "j")])
(apply mapv #(zipmap [:foo :bar :biz] %&) input)
;;=> [{:foo "a", :bar "b", :biz "c"}
;; {:foo "d", :bar "e", :biz "f"}
;; {:foo "h", :bar "i", :biz "j"}]
map can take operate on multiple sequences. When given multiple sequences, it will take elements from each and call your function with them like so:
(let [s1 '("a" "d" "h")
s2 '("b" "e" "i")
s3 '("c" "f" "j")]
(map (fn [x y z]
{:foo x :bar y :baz z})
s1 s2 s3))
;; =>
({:foo "a", :bar "b", :baz "c"}
{:foo "d", :bar "e", :baz "f"}
{:foo "h", :bar "i", :baz "j"})
I'm sorry about the lack of precision in the title, but it might illustrate my lack of clojure experience.
I'm trying to take a large list of strings, and convert that list into another list of strings, concatenating as I go until the accumulator is less than some length.
For example, if I have
[ "a" "bc" "def" "ghij" ]
and my max string length is 4, I would walk down the list, accumulating the concat, until my accumulation len > 4, and then start the accumulator from scratch. My result would look like:
[ "abc" "def" "ghij" ]
I can't seem to come up with the proper incantation for partition-by, and it's driving me a little crazy. I've been trying to make my accumulator an atom (but can't seem to figure out where to reset!), but other than that, I can't see where/how to keep track of my accumulated string.
Thanks in advance to anyone taking mercy on me.
(defn catsize [limit strs]
(reduce (fn [res s]
(let [base (peek res)]
(if (> (+ (.length ^String base) (.length ^String s)) limit)
(conj res s)
(conj (pop res) (str base s)))))
(if (seq strs) [(first strs)] [])
(rest strs)))
Here's my take on this:
(defn collapse [maxlen xs]
(let [concats (take-while #(< (count %) maxlen) (reductions str xs))]
(cons (last concats) (drop (count concats) xs))))
(collapse 4 ["a" "bc" "def" "ghij"])
;; => ("abc" "def" "ghij")
This gets pretty close. I'm not sure why you have j at the end of the final string.
(sequence
(comp
(mapcat seq)
(partition-all 3)
(map clojure.string/join))
["a" "bc" "def" "ghij"]) => ("abc" "def" "ghi" "j")
Here is how I would do it:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(def bound 4)
(defn catter [strings-in]
(loop [merged-strs []
curr-merge (first strings-in)
remaining-strs (rest strings-in)]
;(newline) (spyx [merged-strs curr-merge remaining-strs])
(if (empty? remaining-strs)
(conj merged-strs curr-merge)
(let ; try using 'let-spy' instead
[new-str (first remaining-strs)
new-merge (str curr-merge new-str)]
(if (< (count new-merge) bound)
(recur merged-strs new-merge (rest remaining-strs))
(recur (conj merged-strs curr-merge) new-str (rest remaining-strs)))))))
(dotest
(is= ["abc" "def" "ghij"] (catter ["a" "bc" "def" "ghij"]) )
(is= ["abc" "def" "ghij"] (catter ["a" "b" "c" "def" "ghij"]) )
(is= ["abc" "def" "ghij"] (catter ["a" "b" "c" "d" "ef" "ghij"]) )
(is= ["abc" "def" "ghij"] (catter ["a" "bc" "d" "ef" "ghij"]) )
(is= ["abc" "def" "ghij"] (catter ["a" "bc" "d" "e" "f" "ghij"]) )
(is= ["abc" "def" "gh" "ij"] (catter ["abc" "d" "e" "f" "gh" "ij"]) )
(is= ["abc" "def" "ghi" "j"] (catter ["abc" "d" "e" "f" "ghi" "j"]) )
(is= ["abcdef" "ghi" "j"] (catter ["abcdef" "ghi" "j"]) )
(is= ["abcdef" "ghi" "j"] (catter ["abcdef" "g" "h" "i" "j"]) )
)
You will need to add [tupelo "0.9.71"] to project dependencies.
Update:
If you user spy and let-spy, you can see the process the algorithm uses to arrive at the result. For example:
(catter ["a" "b" "c" "d" "ef" "ghij"]) ) => ["abc" "def" "ghij"]
-----------------------------------------------------------------------------
strings-in => ["a" "b" "c" "d" "ef" "ghij"]
[merged-strs curr-merge remaining-strs] => [[] "a" ("b" "c" "d" "ef" "ghij")]
new-str => "b"
new-merge => "ab"
[merged-strs curr-merge remaining-strs] => [[] "ab" ("c" "d" "ef" "ghij")]
new-str => "c"
new-merge => "abc"
[merged-strs curr-merge remaining-strs] => [[] "abc" ("d" "ef" "ghij")]
new-str => "d"
new-merge => "abcd"
[merged-strs curr-merge remaining-strs] => [["abc"] "d" ("ef" "ghij")]
new-str => "ef"
new-merge => "def"
[merged-strs curr-merge remaining-strs] => [["abc"] "def" ("ghij")]
new-str => "ghij"
new-merge => "defghij"
[merged-strs curr-merge remaining-strs] => [["abc" "def"] "ghij" ()]
Ran 2 tests containing 10 assertions.
0 failures, 0 errors.
Suppose we have a map m with the following structure:
{:a (go "a")
:b "b"
:c "c"
:d (go "d")}
As shown, m has four keys, two of which contain channels.
Question: How could one write a general function (or macro?) cleanse-map which takes a map like m and outputs its channeless version (which, in this case, would be {:a "a" :b "b" :c "c" :d "d"})?
A good helper function for this question might be as follows:
(defn chan? [c]
(= (type (chan)) (type c)))
It also doesn't matter if the return value of cleanse-map (or whatever it's called) is itself a channel. i.e.:
`(cleanse-map m) ;=> (go {:a "a" :b "b" :c "c" :d "d"})
Limitations of core.async make implementation of cleanse-map not that straightforward. But the following one should work:
(defn cleanse-map [m]
(let [entry-chs (map
(fn [[k v]]
(a/go
(if (chan? v)
[k (a/<! v)]
[k v])))
m)]
(a/into {} (a/merge entry-chs))))
Basically, what is done here:
Each map entry is transformed to a channel which will contain this map entry. If value of map entry is a channel, it is extracted inside go-block within mapping function.
Channels with map-entries are merge-d into single one. After this step you have a channel with collection of map entries.
Channel with map entries is transformed into channel that will contain needed map (a/into step).
(ns foo.bar
(:require
[clojure.core.async :refer [go go-loop <!]]
[clojure.core.async.impl.protocols :as p]))
(def m
{:a (go "a")
:b "b"
:c "c"
:d (go "d")
:e "e"
:f "f"
:g "g"
:h "h"
:i "i"
:j "j"
:k "k"
:l "l"
:m "m"})
(defn readable? [x]
(satisfies? p/ReadPort x))
(defn cleanse-map
"Takes from each channel value in m,
returns a single channel which will supply the fully realized m."
[m]
(go-loop [acc {}
[[k v :as kv] & remaining] (seq m)]
(if kv
(recur (assoc acc k (if (readable? v) (<! v) v)) remaining)
acc)))
(go (prn "***" (<! (cleanse-map m))))
=> "***" {:m "m", :e "e", :l "l", :k "k", :g "g", :c "c", :j "j", :h "h", :b "b", :d "d", :f "f", :i "i", :a "a"}
I expected this code snippet to produce the original vector, but sorted in a case-insensitive way. Instead I get the original vector untouched. Why doesn't my comparator work?
user=> (ns user (require [clojure.contrib.string :as str]))
nil
user=> (sort
(comparator #(compare (str/upper-case %1) (str/upper-case %2)))
["B" "a" "c" "F" "r" "E"])
("B" "a" "c" "F" "r" "E")
comparator returns a java.util.Comparator when given a predicate (a function which returns true or false). You don't need it if you're using compare explicitly. So just:
(sort #(compare (str/upper-case %1) (str/upper-case %2))
["B" "a" "c" "F" "r" "E"])
;=> ("a" "B" "c" "E" "F" "r")
Alternatively, use sort-by:
(sort-by str/upper-case ["B" "a" "c" "F" "r" "E"])
;=> ("a" "B" "c" "E" "F" "r")
compare is not a predicate, it's a comparator.