I have this function:
(defn dissoc-all [m kv]
(let [[k & ks] kv]
(dissoc m k ks)))
Where m is the map and kv is the vector of keys. I use it like this:
(dissoc-all {:a 1 :b 2} [:a :b])
=>{:b 2}
This is not what I've expected. ks has :b but I don't know why it is not being use by dissoc. Anyone can help me with this?
Edit: Added question is that why is this not triggering the 3rd overload of dissoc, which is dissoc [map key & ks]?
Changed name from dissoc-in to dissoc-all as noisesmith have said, -in is not a proper name for this and I agree.
This won't work because ks is a collection of all the elements in kv after the first. So instead of :b it is [:b].
Instead, you can just use apply:
(defn dissoc-in [m vs]
(apply dissoc m vs))
Also, dissoc-in is an odd name for this function, because the standard functions with -in in the name all do nested access, and this does not use the keys to do any nested access of the map.
Why not something like this?
(defn dissoc-all [m ks]
(apply dissoc m ks))
(dissoc-all {:a 1 :b 2} [:a :b])
=> {}
The reason the third overlod of dissoc is not getting called is because it does not expect a collection of keys like [:a :b] - it expects just the keys.
For example:
(dissoc {:a "a" :b "b" :c "c" :d "d"} :a :b :c)
=> {:d "d"}
Further to noisesmith's answer:
You're being confused by the overloads/arities of dissoc, which have this simple effect:
[m & ks]
"Returns a new map of the same (hashed/sorted) type,
that does not contain a mapping for any of ks. "
The explicit arities for no keys and one key are for performance. Many clojure functions are so organised, and the documentation follows the organisation, not the underlying idea.
Now, the action of
(dissoc-all {:a 1 :b 2} [:a :b])
;{:b 2}
is to bind
k to :a
ks to [:b]
Note the latter. The example removes the :a but fails to remove the [:b], which isn't there.
You can use apply to crack open ks:
(defn dissoc-all [m kk]
(let [[k & ks] kk]
(apply dissoc m k ks)))
(dissoc-all {:a 1 :b 2} [:a :b])
;{}
... or, better, do as #noisesmith does and short-circuit the destructuring, using apply at once.
Related
In clojure, given a data structure [{:a "foo" :b "bar"} {:a "biz" :b "baz"}] how would I get [{:b "bar"}{:b "baz"}] the most succinctly?
dissoc is a function for dissociating a key from an associative structure like a map. Here's how you'd do it with one map:
(dissoc my-map :a)
If you have a sequence of maps, you can map a function over them to dissoc the key(s) from each map:
(map #(dissoc % :a) the-maps)
This phrasing passes an anonymous function to map, but depending on usage you may want to extract a named function:
(defn fix-the-map [m]
(dissoc m :a))
(map fix-the-map the-maps)
#Taylor's above answer to dissoc :a from each map is fine if you want all maps without :a.
In case if you want a list of maps with just :b key, you can do
<!-- language-all: lang-clj -->
;; Assuming my-map is the object map
;; map returns a lazy sequence
(map #(hash-map :b (:b %)) my-map)
;; or
(map #(select-keys % [:b]) mp)
user=> (into {} '((:a :b) (:c :d)))
Throws: ClassCastException clojure.lang.Keyword cannot be cast to java.util.Map$Entry clojure.lang.ATransientMap.conj (ATransientMap.java:44).
Whereas:
user=> (into {} (list [:a :b] [:c :d]))
Is fine. It's a strange difference, since many times other functions return lists when the thing they had to begin with was a vector:
user=> (into {} (partition 2 (interleave [:a :b] [:c :d])))
Will throw, because it partition 2 ...) results in ((:a :c) (:b :d)). So it's pretty annoying. You basically have to memorize both the return types of methods and the specific behaviors of functions like into, or you have to just let stuff blow up and fix it as you find it with stuff like (into {} (map vec (partition 2 (interleave [:a :b] [:c :d])))).
Is there a specific reason why into doesn't like the pairs as lists?
The reason is as you state, only a vector pairs can be used to build maps. I don't know of a practical reason why this limitation exists. But there are also several other methods for constructing hash-maps. If you find yourself using partition, perhaps the answer is to use an alternate construction method.
If you have parallel sequences of keys and values:
(zipmap [:a :c] [:b :d])
If you have all the items in a flat sequence:
(apply hash-map [:a :b :c :d])
Building a map from a sequence:
(into {} (for [[k v] xs]
[k (transform v)]))
I never realized this wouldn't work! Don't forget:
(apply hash-map (interleave [:a :b] [:c :d]))
;=> {:b :d, :a :c}
since hash-map implicitly creates pairs from the scalar args:
(hash-map :a :c :b :d)
;=> {:b :d, :a :c}
you don't really need the (partition 2...) which is the source of the problem.
I've read this kind of thing a couple of times since I've started Clojure.
For instance, here: How to convert map to a sequence?
And in some tweet I don't remember exactly that was more or less saying "if you're using flatten you're probably doing it wrong".
I would like to know, what is wrong with flatten?
I think this is what they were talking about in the answer you linked:
so> ((comp flatten seq) {:a [1 2] :b [3 4]})
(:b 3 4 :a 1 2)
so> (apply concat {:a [1 2] :b [3 4]})
(:b [3 4] :a [1 2])
Flatten will remove the structure from the keys and values, which is probably not what you want. There are use cases where you do want to remove the structure of nested sequences, and flatten was written for such cases. But for destructuring a map, you usually do want to keep the internal sequences as is.
Anything flatten can't flatten, it ought to return intact. At the top level, it doesn't.
(flatten 8)
()
(flatten {1 2, 3 4})
()
If you think you've supplied a sequence, but you haven't, you'll get the effect of supplying an empty sequence. This is the sort of leg-breaker that most core functions take care to preclude. For example, (str nil) => "".
flatten ought to work like this:
(defn flatten [x]
(if (sequential? x)
((fn flat [y] (if (sequential? y) (mapcat flat y) [y])) x)
x))
(flatten 8)
;8
(flatten [{1 2, 3 4}])
;({1 2, 3 4})
(flatten [1 [2 [[3]] 4]])
;(1 2 3 4)
You can find Steve Miner's faster lazy version of this here.
Probability of "probably"
Listen to people who say "you're probably doing it wrong", but also do not forget they say "probably", because it all depends on the problem.
For example if your task is to flatten the map where you could care less what was the key what was the value, you just need an unstructured sequence of all, then by all means, use flatten (or apply concat).
The reason it causes a "suspicion" is the fact that you had / were given a map to begin with, hence whoever gave it to you meant a "key value" paired structure, and if you flatten it, you lose that intention, as well as flexibility and clarity.
Keep in mind
In case you are still not sure what to do with a map for you particular problem, have a for comprehension in mind, since you would have a full control on what to do with the map as you iterate of it:
create a vector?
;; can also be (apply vector {:a 34 :b 42}), but just to use "for" for all consistently
user=> (into [] (for [[k v] {:a 34 :b 42}] [k v]))
[[:a 34] [:b 42]]
create another map?
user=> (into {} (for [[k v] {:a 34 :b 42}] [k (inc v)]))
{:a 35, :b 43}
create a set?
user=> (into #{} (for [[k v] {:a 34 :b 42}] [k v]))
#{[:a 34] [:b 42]}
reverse keys and values?
user=> (into {} (for [[k v] {:a 34 :b 42}] [v k]))
{34 :a, 42 :b}
I have the following functions and reduced sample:
(defn parse-time
[time-str]
(->> time-str
(re-find #"(\d{1,2}):(\d{2}):(\d{2})")
...))
(defn coerce-times
[m & ks]
(update-in m ks parse-time))
(coerce-times {:depart "05:05:00" :arrive "05:05:00"} :depart :arrive)
This works as expected with only one key, but when I try to use multiple keys (as in the example above), I get a NPE. Line 20 is the re-find line.:
java.lang.NullPointerException: null
at java.util.regex.Matcher.getTextLength (Matcher.java:1234)
java.util.regex.Matcher.reset (Matcher.java:308)
java.util.regex.Matcher.<init> (Matcher.java:228)
java.util.regex.Pattern.matcher (Pattern.java:1088)
clojure.core$re_matcher.invoke (core.clj:4460)
clojure.core$re_find.invoke (core.clj:4512)
tempest.core$parse_time.invoke (core.clj:20)
...
Can someone please help me understand what I'm doing wrong and how I can fix this?
The keys vector provided to update-in is not a collection of keys to operate on, but a series of lookups to follow:
user> (update-in {:a {:b {:c 0}}} [:a :b :c] inc)
{:a {:b {:c 1}}}
I'm following this example: http://groups.google.com/group/clojure/browse_thread/thread/99b3d792b1d34b56
(see the last reply)
And this is the cryptic error that I get:
Clojure 1.2.1
user=> (def m {:a "x" :b "y" :c "z" :d "w"})
#'user/m
user=> (filter #(some % [:a :b]) m)
java.lang.IllegalArgumentException: Key must be integer
(user=>
Also I don't understand why this would even work. Isn't (some ...) going to return the first matching value, "x", every time? I'm a total noob at clojure and just trying to learn.
Please enlighten me.
I guess I just needed to read the docs more:
(select-keys m [:a :b])
Although I'm still not sure what the intention was with the example I found...
If you "iterate" over a map, you'll get key-value pairs rather than keys. For instance,
user=> (map #(str %) {:a 1, :b 2, :c 3})
("[:a 1]" "[:b 2]" "[:c 3]")
Thus your anonymous function tries to evaluate (some [:a "x"] [:a :b]) which clearly does not work.
The ideomatic solution is to use select-keys as mentioned in another answer.
(filter
(fn [x]
(some #{(key x)} [:a :b])) m)
Would do the same using filter and some (but uglier and slower).
This works by filter all from m if some [:a :b] is in the set #{(key x)} (i.e. using a set as predicate) then return the map entry.