replace vector using map in clojure - clojure

I have an vector and a map. And want to replace the vector element if it is an map key (replace the key with value)
user=> (def v [:a :b :c :d])
#'user/v
user=> (def m {:a :k, :c :q} )
#'user/m
user=> (reduce (fn[x y] (conj x (if (y m) (y m) y))) [] v)
[:k :b :q :d]
Is there any better way to do it?

Since your input and output are collections of the same length and the collection items are calculated independently, it would be simpler and more idiomatic to use map, or mapv for vector output.
(mapv (fn [x] (m x x))
v)
or simply
(mapv #(m % %) v)
Note that (m x x) is similar to (if (contains? m x) (m x) x).

replace does exactly what you want :
user> (replace {:a :x :b :y} [:a :b :c :d :e :f])
[:x :y :c :d :e :f]
Note it works with vectors as an associative collection where keys are indices :
user> (replace [:a :b :c :d] [0 2 4 3 2 1])
[:a :c 4 :d :c :b]

Related

How to merge list of maps in clojure

I am given a list maps:
({:a 1 :b ["red" "blue"]} {:a 2 :b ["green"]} {:a 1 :b ["yellow"]} {:a 2 :b ["orange"]})
and I need to combine them to ultimately look like this:
({:a 1 :b ["red" "blue" "yellow"]} {:a 2 :b ["green" "orange"]})
Where the maps are combined based off the value of the key "a".
So far, I have this
(->> (sort-by :a)
(partition-by :a)
(map (partial apply merge)))
But the merge will overwrite the vector in "b" with the last one giving me
({:a 1 :b ["yellow"]} {:a 2 :b ["orange"]})
Instead of merge, use merge-with, i.e.
(->> a (sort-by :a)
(partition-by :a)
(map (partial
apply
merge-with (fn [x y] (if (= x y) x (into x y))))))
Outputs
({:a 1, :b ["red" "blue" "yellow"]} {:a 2, :b ["green" "orange"]})
You don't really want to sort or partition anything: that is just CPU time spent on nothing, and at the end you have an awkward data structure (a list of maps) instead of something that would be more convenient, a map keyed by the "important" :a value.
Rather, I would write this as a reduce, where each step uses merge-with on the appropriate subsection of the eventual map:
(defn combine-by [k ms]
(reduce (fn [acc m]
(update acc (get m k)
(partial merge-with into)
(dissoc m k)))
{}, ms))
after which we have
user=> (combine-by :a '({:a 1 :b ["red" "blue"]}
{:a 2 :b ["green"]}
{:a 1 :b ["yellow"]}
{:a 2 :b ["orange"]}))
{1 {:b ["red" "blue" "yellow"]}, 2 {:b ["green" "orange"]}}
which is usefully keyed for easily looking up a map by a specific :a, or if you prefer to get the results back out as a list, you can easily unroll it.

Clojure - Count occurences of nested key in nested map?

I have a nested map like so:
{:A {:B {:A {:B {:A {:B 0}}}}}}
I want to count the occurrences of the pair [:A :B] so that in the case above, the result is 3.
My initial idea was to use clojure.walk/postwalk to traverse the map and increment a counter.
Is there a more optimal way of achieving this?
tree-seq works out nice in this case:
(defn count-ab [data]
(->> data
(tree-seq coll? seq)
(keep #(get-in % [:A :B]))
count))
user> (def data {:A {:B {:A {:B {:A {:B 0}}}}}})
#'user/data
user> (count-ab data)
3
user> (def data-1 {:A {:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-1
user> (count-ab data-1)
3
user> (def data-2 {:A {:X {:A {:B 100}}
:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-2
user> (count-ab data-2)
4
Because it's nested map, my pragmatic idea is to traverse recursively and count:
(defn do-count [m]
(loop [m m
counter 0]
(if-let [m* (get-in m [:A :B])]
(recur m* (inc counter))
counter)))
(do-count {:A {:B {:A {:B {:A {:B 0}}}}}}) ;==> 3
I would suggest writing a function to turn the deeply nested map into a flat map keyed by paths
(defn flatten-map
"unwind deeply nested map into map of paths and vals"
([m] (into {} (flatten-map [] m)))
([path m]
(if (map? m)
(mapcat (fn [[k v]]
(flatten-map (conj path k) v))
m)
[[path m]])))
you can use this to count the adjacent [:a :b] keys like this
(->> {:A {:B {:A {:B {:A {:B 0}}}}}}
flatten-map
keys
(mapcat #(partition 2 1 %))
(filter #(= [:A :B] %))
count)

How to select keys in nested maps in Clojure?

Let's say I have a map (m) like this:
(def m {:a 1 :b 2 :c {:d 3 :e 4} :e { ... } ....})
I'd like to create a new map only containing :a, :b and :d from m, i.e. the result should be:
{:a 1 :b 2 :d 3}
I know that I can use select-keys to easily get :a and :b:
(select-keys m [:a :b])
But what's a good way to also get :d? I'm looking for something like this:
(select-keys* m [:a :b [:c :d]])
Does such a function exists in Clojure or what's the recommended approach?
In pure Clojure I would do it like this:
(defn select-keys* [m paths]
(into {} (map (fn [p]
[(last p) (get-in m p)]))
paths))
(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}
I prefer keeping the type of a path regular, so a sequence of keys for all paths. In clojure.spec this would read as
(s/def ::nested-map (s/map-of keyword?
(s/or :num number? :map ::nested-map)))
(s/def ::path (s/coll-of keyword?))
(s/fdef select-keys*
:args (s/cat :m ::nested-map
:paths (s/coll-of ::path)))
As an alternative you can use destructing on a function, for example:
(def m {:a 1 :b 2 :c {:d 3 :e 4}})
(defn get-m
[{a :a b :b {d :d} :c}]
{:a 1 :b b :d d})
(get-m m) ; => {:a 1, :b 2, :d 3}
You can use clojure.walk.
(require '[clojure.walk :as w])
(defn nested-select-keys
[map keyseq]
(w/postwalk (fn [x]
(if (map? x)
(select-keys x keyseq)
(identity x))) map))
(nested-select-keys {:a 1 :b {:c 2 :d 3}} [:a :b :c])
; => {:a 1, :b {:c 2}}
I'm not aware of such a function being part of Clojure. You'll probably have to write it yourself. I've came up with this :
(defn select-keys* [m v]
(reduce
(fn [aggregate next]
(let [key-value (if (vector? next)
[(last next)
(get-in m next)]
[next
(get m next)])]
(apply assoc aggregate key-value)))
{}
v))
Require paths to be vectors so you can use peek (much faster than last). Reduce over the paths like this:
(defn select-keys* [m paths]
(reduce (fn [r p] (assoc r (peek p) (get-in m p))) {} paths))
(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}
Of course, this assumes that all your terminal keys are unique.

Can I refer to a clojure hashmap value from another value in the same map?

I'm trying to come up with some way for the values in a clojure hashmap to refer to each other. Conceptually, something like this:
(def m {:a 1 :b 5 :c (+ (:a m) (:b m))} ;Implies (= (:c m) 6)
This doesn't work, of course since I'm circularly referencing m. I can do something like
(def m {:a 1 :b 5 :c (fn [a b] (+ a b))})
((:c m) (:a m) (:b m)) ;=> 6
but that doesn't really gain anything because I still have to know which a and b to put into the function. Another attempt:
(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
((:c m) m) ;=> 6
It's a bit better since I've now internalized the function to a map though not specifically this map. I might try to fix that with something like this
(defn new-get [k m]
(let [v-or-fn (get m k)]
(if (fn? v-or-fn) (v-or-fn m) v-or-fn)))
(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
(new-get :a m) ;=> 1
(new-get :b m) ;=> 5
(new-get :c m) ;=> 6
I think this is about the best I can do. Am I missing something more clever?
Couldn't help myself from writing a macro:
(defmacro defmap [name m]
(let [mm (into [] (map (fn [[k v]] `[~k (fn [~name] ~v)]) m))]
`(def ~name
(loop [result# {} mp# (seq ~mm)]
(if (seq mp#)
(let [[k# v#] (first mp#)]
(recur (assoc result# k# (v# result#)) (rest mp#)))
result#)))))
(defmap m [[:a 1]
[:b 5]
[:c (+ (:a m) (:b m))]])
;; m is {:a 1 :b 5 :c 6}
As I've already said in comment above you can use let form:
(def m
(let [a 1 b 5]
{:a a :b b :c (+ a b)}))
This should be fine if you're using values that known only inside m definition. Otherwise you would better to use function parameters as #Michiel shown.
P.S. by the way you're free to use everything inside def you're usually use in clojure. Moreover, sometimes you're free to use let in sugared form inside some other forms (although this let uses different mechanisms than usual let form):
(for [x (...) xs]
:let [y (+ x 1)]
; ...
Since c is a derived value, so a function, of a and b you're probably better of by defining a function that produces this map:
(defn my-map-fn [a b]
{:a a :b b :c (+ a b)})
(def my-map (my-map-fn 1 2))
(:c my-map) ;;=> 3
Here is my take on it:
(defmacro let-map [& bindings]
(let [symbol-keys (->> bindings (partition 2) (map first))]
`(let [~#bindings]
(into {} ~(mapv (fn [k] [(keyword k) k]) symbol-keys)))))
;; if you view it as similar to let, when it's more complicated:
(let-map
a 1
b 5
c (+ a b)) ; => {:a 1, :b 5, :c 6}
;; if you see it as an augmented hash-map, when it's simple enough:
(let-map a 1, b 5, c (+ a b)) ; => {:a 1, :b 5, :c 6}

How do I conj to a clojure vector conditionally

Is there a cleaner way to do something like the following in clojure?
(defn this [x] (* 2 x))
(defn that [x] (inc x))
(defn the-other [x] (-> x this that))
(defn make-vector [thing]
(let [base (vector (this (:a thing))
(that (:b thing)))]
(if-let [optional (:c thing)]
(conj base (the-other optional))
base)))
(make-vector {:a 1, :b 2}) ;=> [2 3]
(make-vector {:a 1, :b 2, :c 3}) ;=> [2 3 7]
By "cleaner" I mean something closer to this:
(defn non-working-make-vector [thing]
(vector (this (:a thing))
(that (:b thing))
(if (:c thing) (the-other (:c thing)))))
(non-working-make-vector {:a 1, :b 2} ;=> [2 3 nil] no nil, please!
(non-working-make-vector {:a 1, :b 2, :c 3} ;=> [2 3 7]
Note that I might want to call some arbitrary function (e.g. this, that, the-other) on any of the keys in thing and place the result in the returned vector. The important thing is that if the key doesn't exist in the map it should not put a nil in the vector.
This is similar to this question but the output is a vector rather than a map so I can't use merge.
(defn this [x] (* 2 x))
(defn that [x] (inc x))
(defn the-other [x] (-> x this that))
(def k-f-map {:a this
:b that
:c the-other})
(def m1 {:a 1 :b 2})
(def m2 {:a 1 :b 2 :c 3})
(defn make-vector [k-f-map m]
(reduce (fn [res [fk fv]]
(if (fk m)
(conj res (fv (fk m)))
res))
[] k-f-map))
(make-vector k-f-map m1)
-> [2 3]
(make-vector k-f-map m2)
-> [2 3 7]
;;; replace [:a :b :c] with a vector of arbitrary functions
;;; of your choice, or perhaps accept a seqable of functions
;;; as an extra argument
(defn make-vector [thing]
(into [] (keep #(% thing) [:a :b :c])))
;;; from the REPL:
(make-vector {:a 1 :b 2})
; => [1 2]
(make-vector {:a 1 :b 2 :c 3})
; => [1 2 3]
Note that keep only throws out nil; false will be included in the output.
or using cond->?
your make-vector function in cond-> version:
(defn make-vector [thing]
(cond-> [(this (:a thing))
(that (:b thing))]
(:c thing) (conj (the-other (:c thing)))))
you can have more conditions or change :a and :b to be optional as well.