This question just came up for me as I was learning about using the shortcut :keys for destructuring a map.
Suppose i have the following map:
(def my-hashmap {:a "A" :b "B" :c "C" :d "D"})
Now i can refer to the values writing
(let [{first-key :a second-key :b} my-hashmap]
(println first-key second-key))
and i get A B. Now using :keys the keys have to be the same as the original ones (without :), or otherwise clojure returns nil? So, for
(let [{:keys [first-key second-key]} my-hashmap]
(println first-key second-key))
you get nil nil.
Am i seeing this wrong or is really this un-logical thing happening in clojure, because both methodes were meant to refer to the keys and thus should work in the same manner? But they don't, because if I write (let [{:keys [a b]} my-hashmap] (println a b)) I get again the right answer!
Your requirement to :keys in the second example can't be met because there is no way to deduce which individual keys should be looked up to have their values bound to the arbitrary symbols you are using.
Notice that maps are associative, but not ordered data structures. Hence if you wish to use different names, they need to be mapped to the names and types their values can be looked up at in the destructored map, as you did in your first example.
You can use both symbols and keywords in the :keys vector that have fully qualified names of keywords in the map. Futhermore, :syms and :strs are available to destructor against keys which are symbols or strings.
Amir,
Not the specific answer to the question however; maps implement Iterableso you can potentialy create a scheme to work with maps without knowing the key names:
(defn map-sequence-example
[& [[[k v] kp2 & remaining] :as mymap] ]
(println "first key = " k " first value = " v)
(println "next key pair = " kp2)
(println "remaining = " remaining)
(println "mapseq = " mymap))
(map-sequence-example (seq {:a 1 :b 2 :c 3}))
first key = :a first value = 1
next key pair = [:b 2]
remaining = ([:c 3])
mapseq = (([:a 1] [:b 2] [:c 3]))
Related
I really like using contains? because it's so terse and readable. I want to see if a set contains maps that have the same key and value pairs of an example that also had other key value pairs. I'm pretty sure contains? won't work here. Is there an alternative? Maybe I'll have to write one (I'm finally getting into the mindset!). For example, if I had
(def some-set #{{:foo "bar" :beep "boop"}{:foo "bar"} {:foo "bar" :hi "there"}})
what would be a quick way to know if it had any maps that matched {:foo "bar" :one "two"} on :foo "bar"?
Edited: Remembering that a map is a collection of key-value vectors, here is an implementation for the predicate submap?:
(defn submap?
"Returns true if subm is a submap of m, false otherwise."
[subm m]
(every? (fn [[k v]] (= (get m k ::not-found) v)) subm))
This predicate can be used to filter any collection:
(filter #(submap? {:a 1 :b 2} %) [{:a 1} {:a 1 :b 2 :c 3}])
=> ({:a 1, :b 2, :c 3})
Original answer
This solution works but is slower than my updated answer, due to the construction of (set m) for large m
(defn submap?
"Returns true if subm is a submap of m, false otherwise."
[subm m]
(let [kvs (set m)]
(every? kvs subm)))
A generic way would be to write a predicate, that checks if a map
contains another map. This can be done using select-keys to only get
a map with certain keys; using the keys from the map to compare and
then just comparing the result will give you that.
(def maps #{{:foo "bar" :beep "boop"} {:foo "bar"} {:foo "bar" :hi "there"} {:foo "baz"}})
(defn submap?
[submap m]
(= (select-keys m (keys submap)) submap))
(println
(filter (partial submap? {:foo "bar"}) maps))
; → ({:foo bar, :beep boop} {:foo bar, :hi there} {:foo bar})
Yet this is just a simple sequential search. This does not (and AFAIR
there is nothing in core to help) utilize your maps being in a set.
Also note, that the order of the result is undefined since the order of
sets is too.
You can find many predicates of this nature and related helper functions in the Tupelo library, in particular:
submap?
submatch?
wild-match?
wild-submatch?
These are especially helpful in writing unit tests. For example, you may only care about certain fields like :body when testing a webserver response, and you want to ignore other fields like the IP address or a timestamp.
The unit tests show the code in action.
The more I write in Clojure, the more I come across the following sort of pattern:
(defn mapkeys [foo bar baz]
{:foo foo, :bar bar, :baz baz})
In a certain sense, this looks like the inverse process that a destructuring like
(let [{:keys [foo bar baz]}] ... )
would achieve.
Is there a "built-in" way in Clojure to achieve something similar to the above mapkeys (mapping name to keyword=>value) - perhaps for an arbitrary length list of names?
No such thing is built in, because it doesn't need to be. Unlike destructuring, which is fairly involved, constructing maps is very simple in Clojure, and so fancy ways of doing it are left for ordinary libraries. For example, I long ago wrote flatland.useful.map/keyed, which mirrors the three modes of map destructuring:
(let [transforms {:keys keyword
:strs str
:syms identity}]
(defmacro keyed
"Create a map in which, for each symbol S in vars, (keyword S) is a
key mapping to the value of S in the current scope. If passed an optional
:strs or :syms first argument, use strings or symbols as the keys instead."
([vars] `(keyed :keys ~vars))
([key-type vars]
(let [transform (comp (partial list `quote)
(transforms key-type))]
(into {} (map (juxt transform identity) vars))))))
But if you only care about keywords, and don't demand a docstring, it could be much shorter:
(defmacro keyed [names]
(into {}
(for [n names]
[(keyword n) n])))
I find that I quite frequently want to either construct a map from individual values or destructure a map to retrieve individual values. In the Tupelo Library I have a handy pair of functions for this purpose that I use all the time:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(let [m {:a 1 :b 2 :c 3}]
(with-map-vals m [a b c]
(spyx a)
(spyx b)
(spyx c)
(spyx (vals->map a b c)))))
with result
; destructure a map into values
a => 1
b => 2
c => 3
; construct a map
(vals->map a b c) => {:a 1, :b 2, :c 3}
P.S. Of course I know you can destructure with the :keys syntax, but it always seemed a bit non-intuitive to me.
I've started to get some functional programming some weeks ago and I'm trying to perform a mapping from a list of maps to a list considering a specific key in clojure.
My list of maps looks like: '({:a "a1" :b "b1" :c "c1"} {:a "a2" :b "b2" :c "c2"} {:a "a3" :b "b3" :c "c3"})
And the output I'm trying to get is: '("b1" "b2" "b3").
I've tried the following:
(doseq [m maps]
(println (list (get m :b))))
And my output is a list of lists (what is expected as I'm creating a list for each iteration). So my question is, how can I reduce this to a single list?
Update
Just tried the following:
(let [x '()]
(doseq [m map]
(conj x (get m :b))))
However, it is still not working. I`m not getting the point as I was expecting to be appending the elements into a empty list
This is a very common pattern in production Clojure code so it's a good place to learn. In general check out the docs on sequences at https://clojure.org/reference/sequences and when faced with similar task, look to see which pattern best fits and explore functions in that group. In this case it's "Process each item of a seq to create a new seq" and the first item listed is map
your example might look like
(map :b my-data)
You have the right idea, but are using the wrong function. doseq is intended only for side effects and always returns nil. The function you are looking for is for, which takes a sequence as input and returns another sequence as output. I generally prefer for over the similar map as for allows you to name the loop variable:
(def data-list
[{:a "a1" :b "b1" :c "c1"}
{:a "a2" :b "b2" :c "c2"}
{:a "a3" :b "b3" :c "c3"}])
(let [result (vec (for [item data-list]
(:b item)))]
(println result) ; print result
result) ; return result from `let` expression
result => ["b1" "b2" "b3"]
If instead you do this:
(println
(doseq [item data-list]
(println (:b item))))
you can see the difference with doseq vs for:
b1 ; loop item #1
b2 ; loop item #2
b3 ; loop item #3
nil ; return value of doseq
Please see https://www.braveclojure.com/ for online details, and buy a good book (or 5) like Getting Clojure, etc.
(doseq [m maps]
(println (list (get m :b))))
In two short lines, you break several general rules of functional programming:
Pass data into a function as arguments, not as references to global
variables.
Don't print the results of computation. Return them as the value of
the function.
Avoid mechanisms such as doseq that work by side-effects.
Despite this, you were not too far from a solution. doseq is essentially a version of for that throws away its result. If we replace doseq with for, and get rid of the println and the list, we get
=> (for [m maps] (get m :b))
("b1" "b2" "b3")
But Arthur Ulfeldt's simple use of map is better.
I have a question regarding two functions, one taking a complete map and the other specific keywords like so:
(def mapVal
{:test "n"
:anotherKey "n"})
(defn functionTest
[& {:keys [test anotherKey] :or {:test "d" :anotherKey "d"}}]
(println :test :anotherKey))
(defn mapFunc
[map]
(functionTest (get-in map [:test :anotherKey])))
The goal would be that all the keys in the parameter map will be passed correctly to the functionTest. Is there anyway this could work? I tried a few things but i just cant get all the keywords and values passed to the functionTest. What i dont want is just the values of the map, it should be passed to the other function with keyword and value.
You're pretty close. A few things should clear it up.
First, when you declare parameters with [& varname] that means varname will be a list containing all the extra parameters. So you don't need to use that '&' here to destructure the input. Instead, you just name which keys you want to have become variables.
Try this:
(defn functionTest
[{:keys [test anotherKey]}]
(println test anotherKey))
And the other problem is using get-in. With get-in you're defining a "path" through nested data structures with that vector. For example, given:
{:first {:a 1 :b 2} :second {:c 3 :d 4}}
You could use get-in to get the value at :second :c with this:
(get-in {:first {:a 1 :b 2} :second {:c 3 :d 4}} [:second :c])
In your case, you don't need to use get-in at all. You just need to pass the entire map. The destructuring you defined in functionTest will handle the rest. Here is what I did that worked:
(defn mapFunc
[map]
(functionTest map))
I would also suggest you not name the that variable 'map' since it conflicts with the map function.
get-in is for accessing nested associative data structures.
(def m {:a {:x 1}})
(get-in m [:a :x]) ;;=> 1
After you destructure a map those values are in scope and are accessed via symbols. Your example should look like this:
(def mapVal
{:test "n"
:anotherKey "n"})
(defn functionTest
[& {:keys [test anotherKey]
:or {:test "d" :anotherKey "d"}}]
(println test anotherKey))
(defn mapFunc
[m]
(apply functionTest (apply concat (select-keys m [:test :anotherKey]))))
(mapFunc mapVal) ;;=> prints: n n
You have to go through this because functionTest is accepting bare key value pairs as optional parameters (the ones to the right of the &), as in:
(functionTest :test "n"
:anotherKey "n" )
;;=> Also prints: n n
select-keys returns a map with only the specified keys:
(select-keys mapVal [:test])
;; => {:test "n"}
applying concat to a map returns a flat seq of keys and values:
(apply concat (select-keys mapVal [:test :anotherKey]))
;; => (:test "n" :anotherKey "n")
apply applies a function to a seq, as though the seq were its argument list:
(+ [1 2 3]) ;;=> Error.
(apply + [1 2 3]) ;; => 6
As a side note, conventionally in Clojure code, snake-case is preferred to camelCase for most names.
I've defined a record with a bunch of fields--some of which are computed, some of which don't map directly to keys in the JSON data I'm ingesting. I'm writing a factory function for it, but I want to have sensible default/not-found values. Is there a better way that tacking on :or [field1 "" field2 "" field3 "" field4 ""...]? I could write a macro but I'd rather not if I don't have to.
There are three common idioms for implementing defaults in constructor functions.
:or destructoring
Example:
(defn make-creature [{:keys [type name], :or {type :human
name (str "unnamed-" (name type))}}]
;; ...
)
This is useful when you want to specify the defaults inline. As a bonus, it allows let style bindings in the :or map where the kvs are ordered according to the :keys vector.
Merging
Example:
(def default-creature-spec {:type :human})
(defn make-creature [spec]
(let [spec (merge default-creature-spec
spec)]
;; ....
))
This is useful when you want to define the defaults externally, generate them at runtime and/or reuse them elsewhere.
Simple or
Example:
(defn make-creature [{:keys [type name]}]
(let [type (or type :human)
name (or name (str "unnamed-" (name type)))]
;; ...
))
This is as useful as :or destructoring but only those defaults are evaluated that are actually needed, i. e. it should be used in cases where computing the default adds unwanted overhead. (I don't know why :or evaluates all defaults (as of Clojure 1.7), so this is a workaround).
If you really want the same default value for all the fields, and they really have to be different than nil, and you don't want to write them down again, then you can get the record fields by calling keys on an empty instance, and then construct a map with the default values merged with the actual values:
(defrecord MyFancyRecord [a b c d])
(def my-fancy-record-fields (keys (map->MyFancyRecord {})))
;=> (:a :b :c :d)
(def default-fancy-fields (zipmap my-fancy-record-fields (repeat "")))
(defn make-fancy-record [fields]
(map->MyFancyRecord (merge default-fancy-fields
fields)))
(make-fancy-record {})
;=> {:a "", :b "", :c "", :d ""}
(make-fancy-record {:a 1})
;=> {:a 1, :b "", :c "", :d ""}
To get the list of record fields you could also use the static method getBasis on your record class:
(def my-fancy-record-fields (map keyword (MyFancyRecord/getBasis)))
(getBasis is not part of the public records api so there are no guarantees it won't be removed in future clojure versions. Right now it's available in both clojure and clojurescript, it's usage is explained in "Clojure programming by Chas Emerick, Brian Carper, Christophe Grand" and it's also mentioned in this thread during a discussion about how to get the keys from a record. So, it's up to you to decide if it's a good idea to use it)