Cleansing a Map of Its Channels - clojure

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"}

Related

How to reference a key in the map it exists in?

I have a map like so:
{:a "some" :b (str :a " stuff")}
What I'm trying to do is to have the :b value of the map be "some stuff", but the above example doesn't work. I tried wrapping it in def;
(def foo {:a "some" :b (str (:a foo) " stuff")})
But that doesn't work either. How do I make this work?
You have to use a let here (either define the map or the value of a). You can not access the map while it is build up.
also, you can employ macros for this case (for fun and education). Like you can make up simple anaphoric macro to do the trick:
(defmacro make-map-ana [& keyvals]
`(-> {}
~#(map (fn [[k v]] `((fn [~'it] (assoc ~'it ~k ~v))))
(partition-all 2 keyvals))))
that is how you use it:
user> (make-map-ana :a "some"
:b (str (:a it) " stuff"))
;;=> {:a "some", :b "some stuff"}
that gets expanded to clojure code this way:
(-> {}
((fn [it] (assoc it :a "some")))
((fn [it] (assoc it :b (str (:a it) " stuff")))))
so it gets reflective, passing the context (namely it) downstream.
(make-map-ana :a :fun
:b it
:c it)
;;=> {:a :fun, :b {:a :fun}, :c {:a :fun, :b {:a :fun}}}
another way to write this macro, is to use the overriding in let bindings:
(defmacro make-map-ana [& keyvals]
`(let [~'it {}
~#(mapcat (fn [[k v]] ['it `(assoc ~'it ~k ~v)])
(partition-all 2 keyvals))]
~'it))
now (make-map-ana :a "some" :b (str (:a it) " stuff")) would expand into this:
(let [it {}
it (assoc it :a "some")
it (assoc it :b (str (:a it) " stuff"))]
it)
also producing the result you need
Well, as cfrick said, I don't think you can access a map while you are building it like you wrote in your second example.
You could build a simple function like the one bellow.
(defn merge-a-b [map] (assoc map :b (str (:a map) (:b map))))
It's also useful if you end with a vector of those maps, like:
(def x2 [{:a "some", :b " stuff"} {:a "some-other", :b " stuff"}])
You can just use the map build-in in Clojure and run it like:
(map merge-a-b x2)
({:a "some", :b "some:a stuff"} {:a "some-other", :b "some-other stuff"})

Clojure: set value as a key

May be, it is a stupid question, but it may help many of newbies. How do I add a key-value pair to the map?
I mean something like:
(defn init-item [v item]
(let [{:keys [id value]} item]
(-> v
(assoc :{ID_AS_A_KEY} value))))
And I get:
(init-item {} {:id "123456789" :value [:name "King" :surname "Leonid"]})
user=> {:123456789 [:name "King" :surname "Leonid"]}
Just don't do it. Use the string itself as your map key. There's no reason to make it a keyword. It's much easier to work with if you leave it alone.
(defn init-item [v item]
(assoc v (:id item) (:value item)))
I think this is what you meant to do:
(defn init-item
[dest-map item]
(let [item-id-str (:id item)
item-val (:value item)
item-id-kw (keyword item-id-str)]
(assoc dest-map item-id-kw item-val)))
(let [all-items {:a 1 :b 2 :c 3}
item-1 {:id "123456789"
:value [:name "King" :surname "Leonid"]}]
(init-item all-items item-1)
;=> {:a 1, :b 2, :c 3, :123456789 [:name "King" :surname "Leonid"]}
Clojure has functions name, symbol, and keyword to convert between strings and symbols/keywords. Since you already have the ID as a string, you just need to call keyword to convert it.
Be sure to always keep a browser tab open to The Clojure CheatSheet.

Translating vector into map

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)))

Find Value of Specific Key in Nested Map

In Clojure, how can I find the value of a key that may be deep in a nested map structure? For example:
(def m {:a {:b "b"
:c "c"
:d {:e "e"
:f "f"}}})
(find-nested m :f)
=> "f"
Clojure offers tree-seq to do a depth-first traversal of any value. This will simplify the logic needed to find your nested key:
(defn find-nested
[m k]
(->> (tree-seq map? vals m)
(filter map?)
(some k)))
(find-nested {:a {:b {:c 1}, :d 2}} :c)
;; => 1
Also, finding all matches becomes a matter of replacing some with keep:
(defn find-all-nested
[m k]
(->> (tree-seq map? vals m)
(filter map?)
(keep k)))
(find-all-nested {:a {:b {:c 1}, :c 2}} :c)
;; => [2 1]
Note that maps with nil values might require some special treatment.
Update: If you look at the code above, you can see that k can actually be a function which offers a lot more possibilities:
to find a string key:
(find-nested m #(get % "k"))
to find multiple keys:
(find-nested m #(some % [:a :b]))
to find only positive values in maps of integers:
(find-nested m #(when (some-> % :k pos?) (:k %)))
If you know the nested path then use get-in.
=> (get-in m [:a :d :f])
=> "f"
See here for details: https://clojuredocs.org/clojure.core/get-in
If you don't know the path in your nested structure you could write a function that recurses through the nested map looking for the particular key in question and either returns its value when it finds the first one or returns all the values for :f in a seq.
If you know the "path", consider using get-in:
(get-in m [:a :d :f]) ; => "f"
If the "path" is unknown you can use something like next function:
(defn find-in [m k]
(if (map? m)
(let [v (m k)]
(->> m
vals
(map #(find-in % k)) ; Search in "child" maps
(cons v) ; Add result from current level
(filter (complement nil?))
first))))
(find-in m :f) ; "f"
(find-in m :d) ; {:e "e", :f "f"}
Note: given function will find only the first occurrence.
Here is a version that will find the key without knowing the path to it. If there are multiple matching keys, only one will be returned:
(defn find-key [m k]
(loop [m' m]
(when (seq m')
(if-let [v (get m' k)]
v
(recur (reduce merge
(map (fn [[_ v]]
(when (map? v) v))
m')))))))
If you require all values you can use:
(defn merge-map-vals [m]
(reduce (partial merge-with vector)
(map (fn [[_ v]]
(when (map? v) v))
m)))
(defn find-key [m k]
(flatten
(nfirst
(drop-while first
(iterate (fn [[m' acc]]
(if (seq m')
(if-let [v (get m' k)]
[(merge-map-vals m') (conj acc v)]
[(merge-map-vals m') acc])
[nil acc]))
[m []])))))

Filtering a map based on expected keys

In my Clojure webapp I have various model namespaces with functions that take a map as an agrument and somehow insert that map into a database. I would like to be able take out only the desired keys from the map before I do the insert.
A basic example of this is:
(let [msg-keys [:title :body]
msg {:title "Hello" :body "This is an example" :somekey "asdf" :someotherkey "asdf"}]
(select-keys msg msg-keys))
;; => {:title "Hello" :body "This is an example"}
select-keys is not an option when the map is somewhat complex and I would like to select a specific set of nested keys:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(some-select-key-fn person [:name [:first] :something [:a :b]]))
;; => {:name {:first "john"} :something {:a "a" :b "b"}}
Is there a way to do this with the core functions? Is there a way do this purely with destructuring?
This topic was discussed in the Clojure Google Group along with a few solutions.
Destructuring is probably the closest to a "core" capability, and may be a fine solution if your problem is rather static and the map has all of the expected keys (thus avoiding nil). It could look like:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}
{{:keys [first]} :name {:keys [a b]} :something} person]
{:name {:first first} :something {:a a :b b}})
;; => {:name {:first "john"}, :something {:a "a", :b "b"}}
Below is a survey of the solutions in the Clojure Google Group thread, applied to your sample map. They each have a different take on how to specify the nested keys to be selected.
Here is Christophe Grand's solution:
(defprotocol Selector
(-select [s m]))
(defn select [m selectors-coll]
(reduce conj {} (map #(-select % m) selectors-coll)))
(extend-protocol Selector
clojure.lang.Keyword
(-select [k m]
(find m k))
clojure.lang.APersistentMap
(-select [sm m]
(into {}
(for [[k s] sm]
[k (select (get m k) s)]))))
Using it requires a slightly modified syntax:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(select person [{:name [:first] :something [:a :b]}]))
;; => {:something {:b "b", :a "a"}, :name {:first "john"}}
Here is Moritz Ulrich's solution (he cautions that it doesn't work on maps with seqs as keys):
(defn select-in [m keyseq]
(loop [acc {} [k & ks] (seq keyseq)]
(if k
(recur
(if (sequential? k)
(let [[k ks] k]
(assoc acc k
(select-in (get m k) ks)))
(assoc acc k (get m k)))
ks)
acc)))
Using it requires another slightly modified syntax:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(select-in person [[:name [:first]] [:something [:a :b]]]))
;; => {:something {:b "b", :a "a"}, :name {:first "john"}}
Here is Jay Fields's solution:
(defn select-nested-keys [m top-level-keys & {:as pairs}]
(reduce #(update-in %1 (first %2) select-keys (last %2)) (select-keys m top-level-keys) pairs))
It uses a different syntax:
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(select-nested-keys person [:name :something] [:name] [:first] [:something] [:a :b]))
;; => {:something {:b "b", :a "a"}, :name {:first "john"}}
Here is Baishampayan Ghose's solution:
(defprotocol ^:private IExpandable
(^:private expand [this]))
(extend-protocol IExpandable
clojure.lang.Keyword
(expand [k] {k ::all})
clojure.lang.IPersistentVector
(expand [v] (if (empty? v)
{}
(apply merge (map expand v))))
clojure.lang.IPersistentMap
(expand [m]
(assert (= (count (keys m)) 1) "Number of keys in a selector map can't be more than 1.")
(let [[k v] (-> m first ((juxt key val)))]
{k (expand v)}))
nil
(expand [_] {}))
(defn ^:private extract* [m selectors expand?]
(let [sels (if expand? (expand selectors) selectors)]
(reduce-kv (fn [res k v]
(if (= v ::all)
(assoc res k (m k))
(assoc res k (extract* (m k) v false))))
{} sels)))
(defn extract
"Like select-keys, but can select nested keys.
Examples -
(extract [{:b {:c [:d]}} :g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11})
;=> {:g 42, :b {:c {:d 1}}}
(extract [:g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11})
;=> {:g 42}
(extract [{:b [:c]} :xxx] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11})
;=> {:b {:c {:d 1, :e 2}}, :xxx 11}
Also see - exclude"
[selectors m]
(extract* m selectors true))
It uses another syntax (and the parameters are reversed):
(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
(extract [{:name [:first]} {:something [:a :b]}] person))
;; => {:name {:first "john"}, :something {:a "a", :b "b"}}
Your best bet is probably to use select keys on each nested portion of the structure.
(-> person
(select-keys [:name :something])
(update-in [:name] select-keys [:first])
(update-in [:something] select-keys [:a :b]))
You could of course use a generalized version of the above to implement the syntax you suggest in a function (with a reduce rather than a -> form, most likely, and recursive calls for each nested selection of keys). Destructuring would not help much, it makes binding nested data convenient, but isn't really that useful for constructing values.
Here is how I would do it with reduce and recursion:
(defn simplify
[m skel]
(if-let [kvs (not-empty (partition 2 skel))]
(reduce (fn [m [k nested]]
(if nested
(update-in m [k] simplify nested)
m))
(select-keys m (map first kvs))
kvs)
m))
note that your proposed argument format is inconvenient, so I changed it slightly
user=> (simplify {:name {:first "john" :john "smith"}
:age 40
:weight 155
:something {:a "a" :b "b" :c "c" :d "d"}}
[:name [:first nil] :something [:a nil :b nil]])
{:something {:b "b", :a "a"}, :name {:first "john"}}
the syntax you propose will require a more complex implementation