Is there a clean, idiomatic way to check that all the values of a map are not empty or 0.
If for instance I have the following map
{"id" 10 "Department" "UI Design" "managerid" 4}
What's the cleanest way to iterate over the values of the map and make sure the strings are not empty ("") or nil and the ints/longs are not 0 or nil.
Essentially I'm trying to validate some input before I commit it to the DB. I know I could use libraries like Prismatic/schema but for now I'd like to know how it could be achieved without this.
This map only contains strings and ints/longs but it could contain other types.
Is there a generic way of doing this?
Multimethods can provide elegant solution for given problem:
; for now dispatch is based only on value type
(defmulti valid? (fn [[k v]] (class v)))
(defmethod valid? java.lang.Long [[_ value]] (not (zero? value)))
(defmethod valid? java.lang.String [[_ value]] (not (empty? value)))
(defmethod valid? nil [_] false)
(defmethod valid? :default [_] true) ; valid for rest cases
To check whole map:
(every? valid? your-map)
Examples:
(every? valid? {:a 1 :b 0}) ; false
(every? valid? {:a 1 :b 1}) ; true
(every? valid? {:a 1 :b ""}) ; false
(every? valid? {:a 1 :b "a"}) ; true
(every? valid? {:a 1 :b "a" :c []}) ; true
Few notes:
(not (empty? value)) can be replaced to (not-empty value) or (seq value), but in both cases full value of string will be returned instead of boolean (which still will evaluate to true of course).
You cannot check number or string for nil because nil has its own type. In example above all nil values are considered as invalid. If for some keys nils are acceptable - dispatch function (fn [[k v]] (class v)) should be changed to also take key into account.
This solution is a bit longer than a simple function like
(defn valid? [[k v]]
(cond (string? v) (not (empty? v))
...
:else true))
but it is more maintainable and extensible.
EDIT. As mentioned in comments, idiomatic way is to use (seq coll) instead of (not (empty? coll)) because empty? is defined like (not (seq coll)). You may still want to keep (not (empty? coll)) check to make validation code more explicit and obvious.
Related
I have (for instance) a mix of data structures such as {:name "Peter" :children "Mark"} and {:name "Mark" :children ["Julia" "John"] i.e. :children value is either a single string or a collection of strings. Other functions in my code expect that the value of :children is always a collection of strings, so I need to adapt the data for them.
Of course I can use something like:
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children
(if (coll? children)
children
[children]))))
But is there a more idiomatic/laconic way?
I think you will have to take no for an answer.
(if (coll? x) x [x]) is about as terse and expressive as it gets. It’s what people usually use for this problem (sometimes with sequential? instead of coll?).
cond-> enthusiasts like me sometimes try to use it in place of a simple conditional, but here it is no improvement:
(cond-> x (not (coll? x)) vector)
In the context of your code, however, you can do a little better. A lookup and association is best expressed with update:
(defn data-adapter [m]
(update m :children #(if (coll? %) % [%])))
the only advice would be to abstract that logic to some function, to keep your actual business logic clean.
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children (ensure-coll children))))
or, more concise, with update:
(defn data-adapter [m]
(update m :children ensure-coll))
where ensure-coll could be something like this:
(defn iffun [check & {:keys [t f] :or {t identity f identity}}]
#((if (check %) t f) %))
(def ensure-coll (iffun coll? :f list))
(or whatever another implementation you like)
user> (data-adapter {:children 1})
;;=> {:children (1)}
user> (data-adapter {:children [1]})
;;=> {:children [1]}
Perhaps not idiomatic, but laconic:
(flatten [x])
https://clojuredocs.org/clojure.core/flatten
How can I add an element to an array-map in Clojure? I tried using assoc but it doesn't get added? I essentially want to set a default value of 0 for any missing items in the entry array-map.
(defn create-entry [doc]
(let [entry (assoc doc "id" (str (java.util.UUID/randomUUID)))]
(if (empty? (get entry "foo")) (assoc entry "foo" 0))
(if (empty? (get entry "bar")) (assoc entry "bar" 0))))
Update after comments from Carcigenicate:
(defn entry [doc]
(as-> (assoc doc "id" (str (java.util.UUID/randomUUID))) e
(if (empty? (get e "foo")) (assoc e "foo" 0) e)
(if (empty? (get e "bar")) (assoc e "bar" 0) e)))
(defn create-entry [doc]
(prn (entry doc)))
You need to starting thinking more functional. Note how all the structures you're using are immutable; they themselves can never change. Your second last line makes a copy of entry, but you never do anything with it; it's just thrown out. There are a few ways of dealing with situations like this where you need to transform a structure over a couple steps:
Just use let:
(let [entry (assoc doc "id" (str (java.util.UUID/randomUUID)))
def-foo (if (empty? (get entry "foo")) (assoc entry "foo" 0) entry)]
(if (empty? (get def-foo "bar")) (assoc def-foo "bar" 0) def-foo)))
Note how the last line uses the def-foo copy, instead of the original entry.
Use a threading macro:
; Create a new binding, e, that will hold the result of the previous form
(as-> (assoc doc "id" (str (java.util.UUID/randomUUID))) e
(if (empty? (get e "foo")) (assoc e "foo" 0) e)
(if (empty? (get e "bar")) (assoc e "bar" 0) e))
e is replaced by whatever the previous form evaluated to.
Note though, that if you ever find yourself using get and assoc on the same object, you might want to consider using update instead, which greatly simplifies everything, especially when paired with the -> threading macro:
(-> (assoc doc "id" (str (java.util.UUID/randomUUID)))
(update "foo" #(if (empty? %) 0 %))
(update "bar" #(if (empty? %) 0 %)))
I had to make some assumptions about what your intent was, because your code has an error that I didn't notice until after I had already submitted my answer. In your original code, your ifs don't evaluate to anything when the condition is false. I'm assuming you just don't want to change anything when they're false.
To supplement Carcigenicate's answer, another suggestion:
I'd use merge or assoc on a map of defaults:
(merge {:default-1 123 :default-2 234} {:default-1 "foo"})
=> {:default-1 "foo", :default-2 234}
Note that the order of arguments to merge matters i.e. right-most maps take precedence over left-most maps. Your default map values will only "survive" if they're not overridden by additional map(s).
(def defaults {"foo" 0, "bar" 0})
(defn create-entry [doc]
(assoc defaults "id" (str (java.util.UUID/randomUUID))))
(defn create-entry [doc]
(merge defaults {"id" (str (java.util.UUID/randomUUID))}))
Using assoc in this example has the same effect, and I'd prefer that version.
I'm generating json as literally as I can in clojure. My problem is that certain branches of the json are only present if given parameters are given. Here is a sample of such a condition
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when generated-uuids (let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches})))}}})
Unfortunately the when/let part is quite ugly. This same could be achieved using when-let as following but it doesn't work because my map returns [] instead of a nil.
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when-let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches}))}}})
Any ideas if I could somehow make when-let consider an empty list/array/seq as false so I could clean up my code a bit?
not-empty returns its argument if it is not empty.
When using when-let with a collection, always use not-empty
to retain the collection type
make refactoring easier
expressivenes
(when-let [batches (not-empty (map ...))]
...)
In your case I'd however prefer something like this:
...
:metadata {:batch (cond-> {:id uuid}
(seq generated-uuids)
(assoc :generatedBatches (map ...)))}
...
Notice that all three of the advantages listed above where met, without a nested let.
Also notice a new advantage
easier to extend with more conditions lateron
seq returns nil on an empty input sequence so you could do:
(when-let [batches (seq (map #(array-map :id %) generated-uuids))]
{:generatedBatches batches}))}}})
I'm trying to handle following DSL:
(simple-query
(is :category "car/audi/80")
(is :price 15000))
that went quite smooth, so I added one more thing - options passed to the query:
(simple-query {:page 1 :limit 100}
(is :category "car/audi/80")
(is :price 15000))
and now I have a problem how to handle this case in most civilized way. as you can see simple-query may get hash-map as a first element (followed by long list of criteria) or may have no hash-mapped options at all. moreover, I would like to have defaults as a default set of options in case when some (or all) of them are not provided explicite in query.
this is what I figured out:
(def ^{:dynamic true} *defaults* {:page 1
:limit 50})
(defn simple-query [& body]
(let [opts (first body)
[params criteria] (if (map? opts)
[(merge *defaults* opts) (rest body)]
[*defaults* body])]
(execute-query params criteria)))
I feel it's kind of messy. any idea how to simplify this construction?
To solve this problem in my own code, I have a handy function I'd like you to meet... take-when.
user> (defn take-when [pred [x & more :as fail]]
(if (pred x) [x more] [nil fail]))
#'user/take-when
user> (take-when map? [{:foo :bar} 1 2 3])
[{:foo :bar} (1 2 3)]
user> (take-when map? [1 2 3])
[nil [1 2 3]]
So we can use this to implement a parser for your optional map first argument...
user> (defn maybe-first-map [& args]
(let [defaults {:foo :bar}
[maybe-map args] (take-when map? args)
options (merge defaults maybe-map)]
... ;; do work
))
So as far as I'm concerned, your proposed solution is more or less spot on, I would just clean it up by factoring out parser for grabbing the options map (here into my take-when helper) and by factoring out the merging of defaults into its own binding statement.
As a general matter, using a dynamic var for storing configurations is an antipattern due to potential missbehavior when evaluated lazily.
What about something like this?
(defn simple-query
[& body]
(if (map? (first body))
(execute-query (merge *defaults* (first body)) (rest body))
(execute-query *defaults* body)))
I'm trying to pull the values out of a complicated list structure.
Given something like this:
[{:a "whatever" :b [:c "foo"]} :e {:f "boo"} :g {:h [:i 62281]}]
I'd like to get:
["whatever" "foo" "boo" 62281]
So far I've only gotten to this point:
((62281) nil (boo) nil whatever foo)
Here's the code:
(defn get-values [params]
(apply conj
(map (fn [part]
(if (not (keyword? part))
(map (fn [v]
(if (vector? v)
(last v)
v))
(vals part))))
params)))
I can't seem to get rid of the nil's
I can't figure out why the values after a certain point are in lists.
I figure there's got to be a better way to do this.
Fix the data structure and everything will fall in place. As of now your data structure isn't consistent at all and that will make any function that touch this data way more complicated and error prone. You can model this data as a map:
(def data {:a "whatever"
:b nil
:c "foo"
:e nil
:f "boo"
:g nil
:h nil
:i 62281})
And then to get the desired result:
(->> (vals data)
(filter (comp not nil?))
(into []))
But for some strange reason you still want to parse the data structure you provided then:
(defn get-values [data]
(->> (map #(if (map? %) (into [] %) %) data)
flatten
(filter #(or (string? %) (number? %)))
(into [])))