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"})
Related
I have a data set that looks like this
[{1 "a"} {2 "b"} {3 "c"}]
I want to transform it into a cummulative map that looks like
{1 "a" 3 "b" 6 "c"}
I think my current approach is long winded. So far I have come up with
(reduce
(fn [sum item]
(assoc sum (+ (reduce + (keys sum))
(key (first item)))
(val (first item))))
split-map)
but the addition on the keys is incorrect. Does anyone know how I can improve on this?
and one more fun version:
(->> data
(reductions (fn [[sum] m] (update (first m) 0 + sum)) [0])
rest
(into {}))
;;=> {1 "a", 3 "b", 6 "c"}
the trick is that reduction function operates on previous and current key-value pairs updating the current pair's key:
(reductions (fn [[sum] m] (update (first m) 0 + sum)) [0] data)
;;=> ([0] [1 "a"] [3 "b"] [6 "c"])
If you fancy transducers:
(require '[net.cgrand.xforms :as xf])
(let [data [{1 "a"} {2 "b"} {3 "c"}]]
(into {} (comp
(map first)
(xf/multiplex [(map last)
(comp (map first) (xf/reductions +) (drop 1))])
(partition-all 2)) data))
=> {1 "a", 3 "b", 6 "c"}
Here is a possible implementation of the computation and it makes extensive use of Clojure sequence functions:
(defn cumul-pairs [data]
(zipmap (rest (reductions ((map (comp key first)) +) 0 data))
(map (comp val first) data)))
(cumul-pairs [{1 "a"} {2 "b"} {3 "c"}])
;; => {1 "a", 3 "b", 6 "c"}
In this code, the expression (rest (reductions ((map (comp first keys)) +) 0 data)) computes the keys of the resulting map and the expression (map (comp first vals) data) computes the values. Then we combine them with zipmap. The function reductions works just like reduce but returns a sequence of all intermediate results instead of just the last one. The curious looking subexpression ((map (comp first keys)) +) is the reducing function, where we use a mapping transducer to construct a reducing function from the + reducing function that will map the input value before adding it.
This is a bit of an awkward problem to solve succintly. Here is one way:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(dotest
(let-spy
[x1 [{1 "a"} {2 "b"} {3 "c"}]
nums (mapv #(first (first %)) x1)
chars (mapv #(second (first %)) x1)
nums-cum (reductions + nums)
pairs (mapv vector nums-cum chars) ; these 2 lines are
result (into {} pairs)] ; like `zipmap`
(is= result {1 "a", 3 "b", 6 "c"})))
By using my favorite template project
we are able to use let-spy from the Tupelo library
and see the results printed at each step:
-----------------------------------
Clojure 1.10.3 Java 15.0.2
-----------------------------------
Testing tst.demo.core
x1 => [{1 "a"} {2 "b"} {3 "c"}]
nums => [1 2 3]
chars => ["a" "b" "c"]
nums-cum => (1 3 6)
pairs => [[1 "a"] [3 "b"] [6 "c"]]
result => {1 "a", 3 "b", 6 "c"}
Ran 2 tests containing 1 assertions.
0 failures, 0 errors.
When it is working with all unit tests, just trim off the -spy part to leave a normal (let ...)form.
Be sure to see this list of documentation sources, especially the Clojure CheatSheet.
Probably the easiest (most readable) version:
(def ml [{1 "a"} {2 "b"} {3 "c"}])
(defn cumsum [l] (reductions + l))
(let [m (into (sorted-map) ml)]
(zipmap (cumsum (keys m)) (vals m)))
;; => {1 "a", 3 "b", 6 "c"}
How about this?
(defn f [v]
(zipmap (reductions + (mapcat keys v)) (mapcat vals v)))
which works with the original vector of maps:
(f [{1 "a"} {2 "b"} {3 "c"}])
;; => {1 "a", 3 "b", 6 "c"}
.. and also with maps of varying length:
(f [{1 "a"} {2 "b"} {3 "c" 4 "d"} {5 "e" 6 "f" 7 "g"}])
;; => {1 "a", 3 "b", 6 "c", 10 "d", 15 "e", 21 "f", 28 "g"}
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.
Given the following vector: ["a" "b" "c"] how can I convert it to [:a :b :c]
You can use mapv:
(mapv keyword ["a" "b" "c"])
(vec (map keyword ["a" "b" "c"]))
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"})
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"}