Clojure : what am I missing from comp? - clojure

I'm currenthly trying to work with compand it looks like I am missing something.
If I understand well, comp works in the same order as the mathematical composition, so (comp g f) is like g(f(x)).
Imagine I have a map like that
(def m {:a 1 :b nil :c 3})
I would like to use remove with a short nil-key? function to remove the entries which have nil values, so :
(into {} (remove nil-key? m)) = {:a 1 :c 3}
I tried to define nil-key? like that :
(defn nil-key? []
(comp nil? second))
It returns an empty map (if I use filter, no map entry is removed)
Maybe I do not understand how the remove function works because I thught there was an hidden map.
Like : 1) first map second on the hashmap
2) tells if the value is nil
3) give the matching
I could do
(into {} (filter second m))
But is also removes false, which I want not.
Of course I can do it easily with a different approach but I would like to understand the comp function.
Thanks !
EDIT
The answer
(def nil-key?
(comp nil? second))
The final function
(defn remove-nil-keys [map]
(->> (remove nil-key? map)
(into {})))

You have a mistake in your nil-key? definition. Your function returns a function that will produce a composed function:
(defn nil-key? []
(comp nil? second))
If you want to use it in this form you would have to call nil-key? in order to produce your predicate function:
(into {} (remove (nil-key?) m))
;; => {:a 1, :c 3}
Instead you should define a var with the result of composing the functions:
(def nil-key? (comp nil? second))
Then it will work correctly:
(into {} (remove nil-key? m))
;; => {:a 1, :c 3}

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.

Python setdefault function in Clojure

I am trying to do something similar in Clojure:
# Increase the count of a feature/category pair
def incf(self,f,cat):
self.fc.setdefault(f,{})
self.fc[f].setdefault(cat,0)
self.fc[f][cat]+=1
Does anybody have idea?
I think a close parallel is fnil which takes a function and some default argument value(s), then returns a function that will use the default value if called with nil argument(s):
(defn incf [fc f cat]
(update-in fc [f cat] (fnil inc 0)))
(incf {} :feature-foo :category-bar)
=> {:feature-foo {:category-bar 1}}
Here we use update-in to update a nested value in the input map, and use fnil to set the default value zero to be incremented if it doesn't exist.
First, you cannot mutate a hash map in Clojure, so there is no exact counterpart for setdefault.
However, if you want to update a nested map increasing the given value (or set to 1, if it is null), you can leverage the fact that clojure.core/get can accept an optional third argument which is the default value (and also, an assoc on nil creates a map):
(defn incf [m cat f]
(let [val (get-in m [cat f] 0)]
(assoc-in m [cat f] (inc val))))
(incf {:my-cat {:a 1}} :my-cat :a) ; returns {:my-cat {:a 2}}
(incf {:my-cat {}} :my-cat :a) ; returns {:my-cat {:a 1}}
(incf {} :my-cat :a) ; returns {:my-cat {:a 1}}

get-in for lists

Apparently get-in doesn't work for '() lists since they're not an associative data structure. This makes sense for the API and from the perspective of performance of large lists. From my perspective as a user it'd be great to still use this function to explore some small test data in the repl. For example I want to be able to:
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-in [1 :a 0]))
=> "one"
Is there some other function that works this way? Is there some other way to achieve this behavior that doesn't involve converting all my lists to (say) vectors?
This does what you ask:
(defn get-nth-in [init ks]
(reduce
(fn [a k]
(if (associative? a)
(get a k)
(nth a k)))
init
ks))
For example,
(-> '({:a "zero"} {:a "one"} {:a "two"})
(get-nth-in [1 :a]))
;"one"
and
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-nth-in [1 :a 0]))
;"one"
The extra 's you have get expanded into (quote ...):
(-> '({:a '("zero" 0)} {:a '("one" 1)} {:a '("two" 2)})
(get-nth-in [1 :a 0]))
;quote
Not what you intended, I think.
A post just yesterday had a problem regarding lazy lists and lazy maps (from clojure/data.xml). One answer was to just replace the lazy bits with plain vectors & maps using this function:
(defn unlazy
[coll]
(let [unlazy-item (fn [item]
(cond
(sequential? item) (vec item)
(map? item) (into {} item)
:else item))
result (postwalk unlazy-item coll)
]
result ))
Since the resulting data structure uses only vectors & maps, it works for your example with get-in:
(let [l2 '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
e2 (unlazy l2) ]
(is= l2 e2)
(is= "one" (get-in e2 [1 :a 0] l2))
)
You can find the unlazy function in the Tupelo library.
The first param for get-in should be a map.
You have to figure out the feature of your sequence, use last, first, filter or some e.g. to get the element first
for example you could use (:a (last data))

how to destructure a map as key and value

Is there a way to destructure a key value pair ? I have a function which take a map as a parameter, I would like to extract the value of both the key and the value in the params itself. How do I do that ?
I can do the following with a vector -
((fn [[a b]] (str a b)) [a b])
How do I do the same / similar with map -
((fn[{k v}] (str k v)) {k v})
Thanks,
Murtaza
map destructuring in functions arg lists is designed for extracting certain keys from a map and giving them names like so:
core> (defn foo [{my-a :a my-b :b}] {my-a my-b})
core/foo
core> (foo {:a 1 :b 2})
{1 2}
i recommend this tutorial. It is a little hard to give a direct equivalent to ((fn[{k v}] (str k v)) {k v}) because the map could have many keys and many values so the destructuring code would be unable to tell which key and value you where looking for. Destructuring by key is easier to reason about.
If you want to arbitrarily choose the first entry in the map you can extract it and use the list destructuring form on a single map entry:
core> (defn foo [[k v]] {v k})
#'core/foo
core> (foo (first {1 2}))
{2 1}
in this example the list destructuring form [k v] is used because first returns the first map entry as a vector.
There are shortcuts available for destructuring maps. For example, if you're looking for specific keys, then you don't have to type out name1 :key1 name1 :key2...
e.g.
main=> (defn fbb [{:keys [foo bar baz]}] (+ foo bar baz))
#'main/fbb
main=> (fbb {:foo 2 :bar 3 :baz 4})
9
instead of...
(defn fbb [{foo :foo bar :bar baz :baz}] (+ foo bar baz))
If your map keys are strings, you can say :strs instead of :keys and if they are symbols you can use :syms.
user=> (for [x (hash-map :a 1 :b 2 :c 3)] (str (first x) " " (second x)))
(":a 1" ":c 3" ":b 2")

Better way to nest if-let in clojure

Say I have a map of this form:
(def m {:a "A" :b "B"})
and I want to do something if :a and :b are both not nil, I can do:
(if-let [a (:a m)]
(if-let [b (:b m)]
... etc ))
or
(if (and (:a m) (:b m))
(let [{a :a b :b} m]
... etc ))
or even
(if (every? m [:a :b])
(let [{a :a b :b} m]
... etc ))
Is there a neater (ie one-line) way to achieve this?
I think a macro may be necessary here to create the behavior you want. I have never written one (yet) but the following representation suggests to me that this might be fairly straightforward:
(let [{:keys [a b]} m]
(when (every? identity [a b])
(println (str "Processing " a " and " b))))
Using the :keys form of destructuring binding and every? enables a single specification of a vector of keys to destructure and check, and the bound locals are available in a following code block.
This could be used to make a macro such as (when-every? [keys coll] code-with-bindings)
I may update this answer with the macro code if I can take the time to work out how to do it.
You could use map destructuring -- a useful feature of Clojure. This also exploits the facts that and is short-circuiting, and any key in the first map not found in the second map gets nil, a falsy value:
(let [{a :a b :b} {:a 1 :b "blah"}]
(and a b (op a b)))
Okay, so it's two lines instead of one .... also this doesn't distinguish between nil and other falsy values.
not-any? is a nice shortcut for this:
user> (not-any? nil? [(m :a) (m :b)])
true
user> (not-any? nil? [(m :a) (m :b) (m :d)])
false
user>
I am not quite sure what you want to do if the keys have non-nil values or whether you want non-nil keys or values returned. So, I just solved it for non-nil keys being returned.
You'd use the following as an intermediate step as part of a final solution.
I'm showing all the steps I used, not to be pedantic, but to provide a complete answer. The namespace is repl-test. It has a main associated with it.
repl-test.core=> (def m {:a "A" :b "B" :c nil})
#'repl-test.core/m
repl-test.core=> (keys m)
(:a :c :b)
and then finally:
; Check key's value to determine what is filtered through.
repl-test.core=> (filter #(if-not (nil? (%1 m)) (%1 m)) (keys m) )
(:a :b)
By the way I found an ugly one-liner, which works because and returns the last thing in its argument list if they're all true:
(if-let [[a b] (and (:a m) (:b m) [(:a m)(:b m)])]
(println "neither " a " nor " b " is falsey")
(println "at least one of " a " or " b " is falsey"))