Given this map:
{:a nil
:b {:c nil
:d 2
:e {:f nil
:g 4}}}
I need a function to remove all nil values, so that the returned map is
{:b {:e {:g 4}
:d 2}}
Or, when given:
{:a nil
:b {:c nil
:d nil
:e {:f nil
:g nil}}}
The result is:
nil
This question has an answer containing a function that supposedly works for nested maps, but that function fails when given a map that is nested more than one level deep.
modification of answer from here https://stackoverflow.com/a/22186735/1393248
(defn remove-nils
"remove pairs of key-value that has nil value from a (possibly nested) map. also transform map to nil if all of its value are nil"
[nm]
(clojure.walk/postwalk
(fn [el]
(if (map? el)
(not-empty (into {} (remove (comp nil? second)) el))
el))
nm))
(defn clean [m]
(if (map? m)
(let [clean-val (fn [[k v]]
(let [v' (clean v)]
(when-not (nil? v')
[k v'])))
m' (->> (map clean-val m)
(remove nil?)
(into {}))]
(when-not (empty? m') m'))
m))
Using specter you can do it like this:
(declarepath DEEP-MAP-VALS)
(providepath DEEP-MAP-VALS (if-path map? [(compact MAP-VALS) DEEP-MAP-VALS] STAY))
(setval [DEEP-MAP-VALS nil?] NONE
{:a nil
:b {:c nil
:d 2
:e {:f nil
:g 4}}})
Please note it will return :com.rpl.specter.impl/NONE instead of nil if nothing is left.
This is partial reuse of this answer
Related
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))
An idiomatic way to set default values in clojure is with merge:
;; `merge` can be used to support the setting of default values
(merge {:foo "foo-default" :bar "bar-default"}
{:foo "custom-value"})
;;=> {:foo "custom-value" :bar "bar-default"}
In reality however, often the default values are not simple constants but function calls. Obviously, I'd like to avoid calling the function if it's not going to be used.
So far I'm doing something like:
(defn ensure-uuid [msg]
(if (:uuid msg)
msg
(assoc msg :uuid (random-uuid))))
and apply my ensure-* functions like (-> msg ensure-uuid ensure-xyz).
What would be a more idiomatic way to do this? I'm thinking something like:
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)
(associf my-map
[:foo :bar] (expensive-func)
:xyz (other-fn))
You can use delay combined with force.
You can then merge your defaults like
(merge {:foo "foo-default" :bar "bar-default" :uuid (delay (random-uuid))}
{:foo "custom-value" :uuid "abc"})
and access values using
(force (:foo ...))
or
(force (:uuid ...))
random-uuid will then only be called when you actually need the value (and only the first time).
You can wrap the call to force in a get-value function, or something like that.
I just adapted the condp macros and wrote the following:
(defmacro assoc-if-nil
"Takes a map as the first argument and a succession of key value pairs that
are used to set the key to value if the key of the map is nil. The value part
is only evaluated if the key is nil (thus different semantics to (merge)).
Example:
(assoc-if-nil {:a {:b :set}}
[:a :b] :non-def
[:a :c] :non-def
:d :non-def)
;; =>{:a {:b :set, :c :non-def}, :d :non-def}"
[m & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
get-fn (fn[kork] (if (vector? kork) `get-in `get))
assoc-fn (fn[kork] (if (vector? kork) `assoc-in `assoc))
pstep (fn [[kork v]] `(if-not (~(get-fn kork) ~g ~kork)
(~(assoc-fn kork) ~g ~kork ~v)
~g))]
`(let [~g ~m ;; avoid double evaluation
~#(interleave (repeat g) (map pstep (partition 2 clauses)))]
~g)))
Which expands to:
(macroexpand-1 '
(assoc-if-nil m
[:a :b] :nested
:d :just-key))
(clojure.core/let
[G__15391 m
G__15391
(clojure.core/if-not
(clojure.core/get-in G__15391 [:a :b])
(clojure.core/assoc-in G__15391 [:a :b] :nested)
G__15391)
G__15391
(clojure.core/if-not
(clojure.core/get G__15391 :d)
(clojure.core/assoc G__15391 :d :just-key)
G__15391)]
G__15391)
I can walk the top-level of the following map using walk in Clojure:
(use 'clojure.walk)
(walk (fn [[k v]] (println (type k) k v)) identity {:a 1 :b {:c 3}})
Result:
clojure.lang.Keyword :b {:c 3}
clojure.lang.Keyword :a 1
{}
(This works in a very similar way to map)
But when I use postwalk - it blows up trying to do destructuring:
(postwalk (fn [[k v]] (println (type k) k v)) {:a 1 :b {:c 3}})
Result:
UnsupportedOperationException nth not supported on this type: Keyword clojure.lang.RT.nthFrom (RT.java:857)
Maybe looking at what happens when you postwalk can shed some light into your issues.
user=> (postwalk println {:a 1 :b {:c 3}})
:a
1
[nil nil]
:b
:c
3
[nil nil]
{}
[nil nil]
{}
nil
user=>
I want to create a map based on variable inputs where a key should only be present if its corresponding value is not nil.
Here's a toy example I came up with:
(defn make-map
[foo bar baz]
(-> {}
(into (and foo {:foo foo}))
(into (and bar {:bar bar}))
(into (and baz {:baz baz}))))
Is there a more accepted/idiomatic way to do this?
I think something like this is a bit more straightforward
(defn make-map
[foo bar baz]
(reduce (fn [m [k v]] (if (some? v) (assoc m k v) m))
{}
{:foo foo :bar bar :baz baz}))
user> (make-map 1 nil 2)
{:baz 2, :foo 1}
user> (make-map nil 1 2)
{:baz 2, :bar 1}
user> (make-map true false true)
{:baz true, :bar false, :foo true}
This uses cond-> to simplify things a little.
(defn make-map
[foo bar baz]
(cond-> {}
foo (assoc :foo foo)
bar (assoc :bar bar)
baz (assoc :baz baz)))
It's hard to tell with the toy example whether there's a better option for you.
(defn make-map [foo bar baz]
(into {}
(filter
#(if-not (nil? (second %)) { (first %) (second %)})
(map vector [ :foo :bar :baz] [for bar baz]))
)
)
For a little bit of variety, a generalisation using for:
(defn some-map
[& args]
(->> (for [[k v] (partition 2 args)
:when (some? v)]
[k v])
(into {})))
Usage:
(some-map :a 1 :b 2 :c nil :d false)
;; => {:a 1, :b 2, :d false}
Or, akin to #noisesmith's answer, something to be applied to an existing map:
(defn some-map
[m]
(into {} (filter (comp some? val) m)))
(some-map {:a 1 :b 2 :c nil :d false})
;; => {:b 2, :d false, :a 1}
You could abstract this to use a syntax and application similar to zipmap so you can have variable argument lists for both keys and args
(defn when-zip
[keys args]
(->> args
(map vector keys)
(remove (comp not second))
(into {})))
(when-zip [:foo :bar :baz :qux] [true nil false 1])
=> {:qux 1, :foo true}
When you don't like the creation of intermediate lazy results you can use Clojure 1.7's transducers or blatantly rip off zipmap's source
(defn when-zip
"Returns a map with each of the keys mapped to
the corresponding val when val is truthy."
[keys vals]
(loop [map {}
ks (seq keys)
vs (seq vals)]
(if (and ks vs)
(recur (if-let [v (first vs)]
(assoc map (first ks) v)
map)
(next ks)
(next vs))
map)))
(when-zip [:foo :bar :baz :qux] [true nil false 1])
=> {:qux 1, :foo true}
If you really still need the original syntax you could then use this to define specific versions
(defn make-map
[& args]
(when-zip [:foo :bar :baz :qux] args))
(make-map true nil false 1)
=> {:qux 1, :foo true}
On the other hand, you could just not bother with removing nils and use zipmap; when you do a map lookup on a non-existing key further on, it will give the same result as a key with value nil anyway:
(:baz {:qux 1, :foo true})
=> nil
(:baz {:qux 1, :baz nil, :bar false :foo true})
=> nil
Of course, this is different with :bar. But usually it's better to do nil and false punning at the consuming stage instead of during transformation.
Just for completeness, here's something closer to what I was trying to reach for originally but didn't quite get.
(defn make-map
[foo bar baz]
(apply hash-map
(concat
(and foo [:foo foo])
(and bar [:bar bar])
(and baz [:baz baz]))))
What is the simplest way to interleave two vectors with n+1 and n members?
(def a [:a :c :e])
(def b [:b :d])
(interleave a b ); truncates to shortest list
[:a :b :c :d]
;what I would like.
(interleave-until-nil a b)
[:a :b :c :d :e]
Cons the first, interleave the rest with arguments reversed.
(cons (first a) (interleave b (rest a)))
;=> (:a :b :c :d :e)
Conj nil to the second, interleave colls get all butlast
(butlast (interleave a (conj b nil)))
;=> (:a :b :c :d :e)
(defn interleave+ [& x]
(take (* (count x) (apply max (map count x)))
(apply interleave (map cycle x))))
(butlast (interleave+ [:a :c :e] [:b :d]))
=> (:a :b :c :d :e)
Tried this as an exercise in lazy seqs. I suspect that there are more elegant ways though.
(defn interleave-all
"interleaves including remainder of longer seqs."
[& seqs]
(if (not-empty (first seqs))
(cons (first (first seqs)) (lazy-seq (apply interleave-all (filter not-empty (concat (rest seqs) [(rest (first seqs))])))))))
If you would like to have nil appended to always have same dimension results, this could be a way to do that:
(defn interleave-all [& seqs]
(reduce
(fn [a i]
(into a (map #(get % i) seqs)))
[]
(range (apply max (map count seqs)))))
For example:
(interleave-all [:a] [:b :c])
outputs:
[:a :b nil :c]
This can be used to transpose a matrix:
(defn matrix-transpose [input]
(partition
(count input)
(apply interleave-all input)))
Example:
(matrix-transpose [[:a] [:b :c]])
Outputs:
[[:a :b] [nil :c]]
Which can be used to i.e. tabular output of lists of differing lengths (but where you need the fixed dimensions to insert nothing where lists have no value for certain indices).