Clojure Specter: how to find map keys that have specific value? - clojure

Eg in a map:
{"test-1" 23,
"test-2" 456,
"test-3" 23}
How to find keys that have value 23?

I think you don't need specter to do that, just filter by value. I.e:
(->> {:key-1 10
:key-2 20
:key-3 10}
(filter (fn [[k v]] (= v 10)))
(map first))
==> [:key-1 :key-3]
A solution with Specter is:
(keys (specter/setval [specter/MAP-VALS #(not= 10 %)]
specter/NONE
{:key-1 10
:key-2 20
:key-3 10}))

If you want to find something using Specter, it is better to use specter/select.
(use 'com.rpl.specter)
(select [ALL #(= (second %) 23) FIRST]
{"test-1" 23,
"test-2" 456,
"test-3" 23})

Related

Clojure - Create an array of keys if value is true

I am totally new to clojure.
I have a JSON like: { "1": true, "2": false, "3": true, "4": false }
I want to create an array of keys for which the value is true in clojure. In this example the array should be ["1", "3"].
Please help me. Any help would be appreciated.
there are also couple of short and simple snippets for that:
user> (filter m (keys m))
;;=> ("1" "3")
user> (keep (fn [[k v]] (when v k)) m)
;;=> ("1" "3")
user> (for [[k v] m :when v] k)
;;=> ("1" "3")
If you're fine with using a vector instead of an array (since you're usually using vectors in Clojure anyway), you can do something like.
(defn keys-for-truthy-vals [m]
(->> m (filter val) (mapv key)))
Note The mapv is only so the map call returns a vector. If you want a seq, just use map.
The same as already provided, just staying in maps.
(keys (filter val m))
If your map is a Something like (->> (filter (fn [[k v]] v) a) (map (fn [[k v]] k))) will work. You can't do it with just a map because you need to drop certain values, so there will need to be some reducing or filtering.
There is built-in function in the Tupelo library for this:
(submap-by-vals map-arg keep-vals & opts)
Returns a new map containing entries with the specified vals. Throws for missing vals,
unless `:missing-ok` is specified. Usage:
(submap-by-vals {:a 1 :b 2 :A 1} #{1 } ) => {:a 1 :A 1}
(submap-by-vals {:a 1 :b 2 :A 1} #{1 9} :missing-ok ) => {:a 1 :A 1}
You could then just use the keys function on the resulting map.
Maybe this?
(->> foo (filter second) keys)
where foo is a map.

Get key by first element in value list in Clojure

This is similar to Clojure get map key by value
However, there is one difference. How would you do the same thing if hm is like
{1 ["bar" "choco"]}
The idea being to get 1 (the key) where the first element if the value list is "bar"? Please feel free to close/merge this question if some other question answers it.
I tried something like this, but it doesn't work.
(def hm {:foo ["bar", "choco"]})
(keep #(when (= ((nth val 0) %) "bar")
(key %))
hm)
You can filter the map and return the first element of the first item in the resulting sequence:
(ffirst (filter (fn [[k [v & _]]] (= "bar" v)) hm))
you can destructure the vector value to access the second and/or third elements e.g.
(ffirst (filter (fn [[k [f s t & _]]] (= "choco" s))
{:foo ["bar", "choco"]}))
past the first few elements you will probably find nth more readable.
Another way to do it using some:
(some (fn [[k [v & _]]] (when (= "bar" v) k)) hm)
Your example was pretty close to working, with some minor changes:
(keep #(when (= (nth (val %) 0) "bar")
(key %))
hm)
keep and some are similar, but some only returns one result.
in addition to all the above (correct) answers, you could also want to reindex your map to desired form, especially if the search operation is called quite frequently and the the initial map is rather big, this would allow you to decrease the search complexity from linear to constant:
(defn map-invert+ [kfn vfn data]
(reduce (fn [acc entry] (assoc acc (kfn entry) (vfn entry)))
{} data))
user> (def data
{1 ["bar" "choco"]
2 ["some" "thing"]})
#'user/data
user> (def inverted (map-invert+ (comp first val) key data))
#'user/inverted
user> inverted
;;=> {"bar" 1, "some" 2}
user> (inverted "bar")
;;=> 1

Pretty-print Nested Hash-map in ClojureScript

Is there a convenient way in ClojureScript to pretty print a nested hash-map in the way that the whole tree-structure becomes immediately visible.
For instance a map like this
(def my-map {:a {:b 1 :c 9} :b {:d 8 :e {:f 2 :g 3 :h 4}} :c 10})
should be printed like this:
{:a {:b 1
:c 9}
:b {:d 8
:e {:f 2
:g 3
:h 4}}
:c 10}
EDIT: There might also be vectors in the map. The usecase is just to inspect larger data structures during development.
There is no built-in way to do it. You might come close to what you want by using cljs.pprint and setting cljs.pprint/*print-right-margin* to a low value.
I would recommend to take a look at a small library shodan which provides a very useful inspect function:
(require '[shodan.inspection :refer [inspect]])
(inspect {:aaaaaa 1
:bbbbbb {:ccc 2
:dddddd [1 2 3 4 5]}})
It won't print anything in your CLJS REPL but will provide a handy view in your browser's console:
You can collapse and expand nested datastructures - it basically does what you asked for.
As a personal challenge I wrote the following code:
(enable-console-print!)
(def atomic? (complement coll?))
(def padding #(apply str (repeat % " ")))
(def tabulate #(apply str (repeat % "\t")))
(def strcat #(->> (apply concat %&) (apply str)))
(defn my-max-key [x] (if (empty? x) [""] (apply (partial max-key count) x)))
(defn longest-key [m] (->> m keys (filter atomic?) (map str) my-max-key))
(def length (comp count str))
(def not-map? (complement map?))
(def nested? #(some coll? %))
(def join #(apply str (interpose % %2)))
(def join-lines (partial join "\n"))
(defn has-atomic? [coll] (some atomic? coll))
(defn diff-key-lengths [key1 key2] (- (length key1) (length key2)))
(defn convert
([thing] (convert -1 thing))
([depth thing]
(defn convert-items []
(defn convert-seq []
(conj []
(map (partial convert (inc depth)) thing)
""))
(defn string-horizontally [[key value]]
(str (tabulate (inc depth))
key
(padding (diff-key-lengths (longest-key thing) key))
" → "
value))
(defn string-vertically [[key value]]
(str (convert (inc depth) key) "\n"
(convert (+ 2 depth) "↓") "\n"
(convert (inc depth) value) "\n"))
(defn convert-kv [[key value]]
(if (nested? [key value])
(string-vertically [key value])
(string-horizontally [key value])))
(cond (atomic? thing)
[(str (tabulate depth) thing)]
(not-map? thing)
(convert-seq)
(map? thing)
(map convert-kv thing)))
(->> (convert-items) flatten join-lines)))
(def sample-input [["the first thing in this nested vector"]
{{"this is a key in a nested map"
"that points to me!!!"}
{"and that entire map points to this map!!!"
"cool!!!"
"but it gets cooler cause..."
"the value's line up!!!"}}])
(->> sample-input convert println)
The terminal output is (psst... the values in a map do line up but I don't think that chrome uses a monospaced font!):

Clojure - how to make reference to an element in a list

I want to know how to make reference to the smallest elements inside a series of lists that i have partitioned like so:
data(map(keyword :counter)querieddata)
sortedlist(sort > tosort)
part(into [] (partition-all (/ (count data) 10) sortedlist))
zi(zipmap [:a :b :c :d :e :f :g :h :i] part)
which gives me results like:
[:a(40 40 36 33) :b(33 30 27 25) :c(25 19 18 5)]
I want to make reference to the smallest number in each list, as for example in the lists above, I would have the number 33 returned for a, 25 for b, and so on.
I would have assumed I could have made reference to the keyword and then used apply max but I am getting an error with this code:
a(map(keyword :a)zi)
minimum(apply min a)
Any help is appreciated greatly!
Your code doesn't look at all like valid Clojure and presuming your input is actually a map and not a vector then this would work:
(into {}
(map (fn [[k v]]
[k (apply max v)])
{:a '(40 40 36 33)
:b '(33 30 27 25)
:c '(25 19 18 5)}))
; => {:a 40, :c 25, :b 33}
Apply min to each element in part and then zipmap the result like you did for zi:
smallest-numbers (map (partial apply min) part)
smallest-numbers-zipped (zipmap [:a :b :c :d :e :f :g :h :i] smallest-numbers)
Also, there's no need to coerce part to a vector, unless it's specifically needed to be a vector elsewhere. The code you've given us will work just as well if part is defined as (partition-all (/ (count data) 10) sortedlist).
Starting at your last result value [:a(40 40 36 33) :b(33 30 27 25) :c(25 19 18 5)] you can use the next function:
(def your-result '[:a(40 40 36 33) :b(33 30 27 25) :c(25 19 18 5)])
(->> (map vec (partition 2 your-result))
(map #(vector (first %) (apply min (second %))))
(into {})
)
To obtain this output
{:a 33 :b 25 :c 5}
And then you can use your keywords as:
(:a (->> (map vec (partition 2 your-result))
(map #(vector (first %) (apply min (second %))))
(into {})
))
=> 33

How do I operate on every item in a vector AND refer to a previous value in Clojure?

Given:
(def my-vec [{:a "foo" :b 10} {:a "bar" :b 13} {:a "baz" :b 7}])
How could iterate over each element to print that element's :a and the sum of all :b's to that point? That is:
"foo" 10
"bar" 23
"baz" 30
I'm trying things like this to no avail:
; Does not work!
(map #(prn (:a %2) %1) (iterate #(+ (:b %2) %1) 0)) my-vec)
This doesn't work because the "iterate" lazy-seq can't refer to the current element in my-vec (as far as I can tell).
TIA! Sean
user> (reduce (fn [total {:keys [a b]}]
(let [total (+ total b)]
(prn a total)
total))
0 my-vec)
"foo" 10
"bar" 23
"baz" 30
30
You could look at this as starting with a sequence of maps, filtering out a sequence of the :a values and a separate sequence of the rolling sum of the :b values and then mapping a function of two arguments onto the two derived sequences.
create sequence of just the :a and :b values with
(map :a my-vec)
(map :b my-vec)
then a function to get the rolling sum:
(defn sums [sum seq]
"produce a seq of the rolling sum"
(if (empty? seq)
sum
(lazy-seq
(cons sum
(recur (+ sum (first seq)) (rest seq))))))
then put them together:
(map #(prn %1 %s) (map :a my-vec) (sums 0 (map :b my-vec)))
This separates the problem of generating the data from processing it. Hopefully this makes life easier.
PS: whats a better way of getting the rolling sum?
Transform it into the summed sequence:
(defn f [start mapvec]
(if (empty? mapvec) '()
(let [[ m & tail ] mapvec]
(cons [(m :a)(+ start (m :b))] (f (+ start (m :b)) tail)))))
Called as:
(f 0 my-vec)
returns:
(["foo" 10] ["bar" 23] ["baz" 30])