compound-key as lookup function in clojure - clojure

In clojure you can use both maps and keys as look up functions hence
({:a 1 :b 2} :a) and (:a {:a 1 :b 2}) are both viable lookup functions.
Why then can you use a map as a lookup function for a compound-key but not the other way around?
This means ({[:compound :mebaby] 1} [:compound :mebaby]}) will return 1, but ([:compound :mebaby] {[:compound :mebaby] 1}) will throw an error.

Keywords implement IFn as one of their features to make them convenient to use as keys. The fn they implement is a lookup for themselves in an associative structure argument. This is not the case for collections like your vector because they implement IFn to do lookup in themselves at the argument key.
So ({[:compound :mebaby] 1} [:compound :mebaby]}) asks the map what the value is for the key [:compound :mebaby], which exists. But ([:compound :mebaby] {[:compound :mebaby] 1}) asks the vector what the value at the index {[:compound :mebaby] 1} is. That's not an integral number, so it can't be an index/key in a vector and throws an error.

The reason is that: your compound-key is no longer a keyword. It is now a vector, though still an IFn, it only takes integers, say i, as arguments, returning the ith element of the vector.
I suspect what you really want is to extract value from a nested map, like extracting the String "c" from {:a {:b "c"}}. If so, these two forms are equivalent:
(get-in {:a {:b "c"}} [:a :b])
;=> "c"
((comp :b :a) {:a {:b "c"}})
;=> "c"

Your assumption that you can use a key as a lookup function is incorrect. You can use a keyword as a lookup function. In your example :a is a keyword. And so it can be used as a lookup function. But [:compound :mebaby] is not a keyword, that's a vector. Vectors can't be used as lookup functions.

Related

Right way to change a value on a map on clojure

Alright, I'm new to clojure, this should be easy but for the life of me I can't find the answer
Let's say I have this map
(def mymap {:a 10 :b 15})
Now I want to change the value of :a to 5. I don't know how to do this properly
I know update and assoc can make changes but they both receive a function as last argument, which applies to the value. I don't want that, I don't want any function to run, I just want to simply set :a to 5.
I think I can pass an anonymous function that simply returns 5 and ignores the arg, but is this the right way? Doesn't look good to me
(update mymap :a (fn [arg] 5))
assoc does not take a function as its last argument; unless you were wanting to associate a function with a key in the map. (assoc mymap :a 5) does what you want.
I'll add though, update, which does take a function, could be used here as well when combined with constantly or just another function (although there's no reason to use them over assoc):
; constantly returns a function that throws away any arguments given to it,
; and "constantly" returns the given value
(update mymap :a (constantly 5))
; Basically the same as above
(update mymap :a (fn [_] 5))
Do keep in mind that as mymap is immutable, so calling (update mymap :a (constantly 5)) or (assoc mymap :a 5) will return a map {:a 5 :b 15}, further references to mymap will continue to return the original value of {:a 10 :b 15}.
If you want to update the value for later calls, you can look at using atoms.
(defonce mymap (atom {:a 10 :b 15}))
(defn change-mymap [value]
(swap! mymap #(assoc % :a value)))
Just make sure that when you want to reference the value of an atom, you dereference it with the # symbol. For example: (clojure.pprint/pprint #mymap)
When you call (change-mymap 5) this will update the stored mymap value to set :a to a new value, leaving any other key-value pairs in your map alone. This can be helpful when you are mapping in updated state in client/server code when responding to inputs from the other system.
Also note that for nested maps, such as
(defonce nested (atom {:a "a value"
:b {:inner "Another value"
:count 3
:another {:value 5}}}))
You can address a particular value in your map by a path vector. You can use the get-in function to retrieve the value (get-in #nested [:b :another :value])
and you can use assoc-in or update-in with a path to update the values. This also allows you to extend a map. For example, with the above value of nested, you can add a whole section to the tree:
(swap! nested #(assoc-in % [:a :b :c :d] "foo"))
will update the initial map to look like this:
{:a {:b {:c {:d "foo"}}}
:b {:inner "Another value"
:count 3
:another {:value 5}}}

Convert a sequence of maps to a map of maps using the value of key in each map

I have a sequence that looks like this: ({:a 1 :b "lorem"} {:a 2 :b "ipsum"}) and I want to convert that to a map of maps using the value of :a as the value of the key in the new map. The result I am expecting is {:1 {:a 1 :b "lorem"} :2 {:a 2 :b "ipsum"}}.
And maybe this is not idiomatic clojure, I'm still learning. I basically receive a large sequence, and I will be looking up values in this sequence by a certain value in each map, and I want to use a map to make it O(1).
In C#, on an IEnumerable, you can call .ToDictionary(x => x.SomeProperty) which would return a Dictionary of key value pairs, using the value of SomeProperty as the key. And this has lookup performance of O(1) instead of the typical O(N) for a list/sequence.
This should do the transform you are after:
(def latin '({:a 1 :b "lorem"} {:a 2 :b "ipsum"}))
(defn transform [kw in]
(zipmap (map kw in) in))
=> (transform :a latin)
{1 {:a 1, :b "lorem"}, 2 {:a 2, :b "ipsum"}}
I hesitated in changing the number to a keyword, as not sure as to the reason you would want to do that...
... edit - because you can always do an indexed (O(1)) retrieve like so:
(get (transform :a latin) 1)
1 doesn't work at the front because it can't be used as a function (unlike :1), however get is a function whose second argument is the search key when the first argument is a map.

Clojure: How to retrieve hash from vector by value

I'm trying to retrieve an entire hash from a vector of hashes based on whether or not it has a specific value in a field.
(def foo {:a 1, :b 2})
(def bar {:a 3, :b 4})
(def baz [foo bar])
In baz, I want to return the entire hash where :a 3 so the result will be {:a 3, :b 4}. I have tried get get-in and find but those rely on keys and do not return the entire hash. I've also tried some suggestion from this question but they don't return the hash either.
filter to the rescue!
hello.core> (def foo {:a 1, :b 2})
#'hello.core/foo
hello.core> (def bar {:a 3, :b 4})
#'hello.core/bar
hello.core> (def baz [foo bar])
#'hello.core/baz
hello.core> (filter #(= (:a %) 3) baz)
({:a 3, :b 4})
#(= (:a %) 3) is a short form for creating an anonymous that takes one argument, named %, in which it will look up the key :a and return true if that matches the value 3. Any entry in the vector baz which passes this test will make it into the output.
PS: a note on pronunciation: that data structure is typically called a "map" because it maps one key to one value. This is terribly confusing because there is also a function named map which changes every member of a sequence by a function.
filter definitely does the job as Arthur mentioned. Just for the sake of completeness these are 2 other solutions which differ in 2 aspects from filter:
(some #(when (= 3 (:a %)) %) baz)
(first (drop-while #(not= 3 (:a %)) baz))
these will stop further searching through your whole collection as soon as they have found the first element in the collection which fits your requirements (hence less resource) and
because of that, in contrary to filter they give you only the first fitting element and not all the elements in the collection which pass your
requirements (in case you have multiple repeated elements in your collection).

Clojure map destructuring in functions that require a single key

I've currently implemented a way to sort by a deep key in a map like so:
(sort-by #(get-in % [:layer :order]) [{:layer {:order 1} {:layer {:order 2}])
I was wondering if there was a way to do this using map destructuring? Is that available for functions outside of let and parameter definition? An example of what I'm wondering is possible:
(sort-by {:layer {:order}} [{:layer {:order 1} {:layer {:order 2}])
As far as I'm aware, you can only destructure within a let binding or function binding. This is how you might do it with nested map destructuring:
(sort-by (fn [{{o :order} :layer}] o)
[{:layer {:order 2}}
{:layer {:order 1}}])
I don't think that's any clearer, though. Since keywords are functions, you could also use plain old function composition:
(sort-by (comp :order :layer)
[{:layer {:order 2}}
{:layer {:order 1}}])
I don't think so because sort-by needs a value extraction function (keyfn)
(that is unless you want to sort entries directly by themself)
user=> (doc sort-by)
-------------------------
clojure.core/sort-by
([keyfn coll] [keyfn comp coll])
Returns a sorted sequence of the items in coll, where the sort
order is determined by comparing (keyfn item). If no comparator is
supplied, uses compare. comparator must implement
java.util.Comparator. If coll is a Java array, it will be modified.
To avoid this, sort a copy of the array.
EDIT: what you can do is "simulate" get-in via compose and keyword lookups as in
(sort-by (comp :a :c) [ {:c {:a 3}} {:c {:a 2}} ])

Why are there so many map construction functions in clojure?

Novice question, but I don't really understand why there are so many operations for constructing maps in clojure.
You have conj, assoc and merge, but they seem to more or less do the same thing?
(assoc {:a 1 :b 2} :c 3)
(conj {:a 1 :b 2} {:c 3})
(merge {:a 1 :b 2} {:c 3})
What's really the difference and why are all these methods required when they do more or less the same thing?
assoc and conj behave very differently for other data structures:
user=> (assoc [1 2 3 4] 1 5)
[1 5 3 4]
user=> (conj [1 2 3 4] 1 5)
[1 2 3 4 1 5]
If you are writing a function that can handle multiple kinds of collections, then your choice will make a big difference.
Treat merge as a maps-only function (its similar to conj for other collections).
My opinion:
assoc - use when you are 'changing' existing key/value pairs
conj - use when you are 'adding' new key/value pairs
merge - use when you are combining two or more maps
Actually these functions behave quite differently when used with maps.
conj:
Firstly, the (conj {:a 1 :b 2} :c 3) example from the question text does not work at all (neither with 1.1 nor with 1.2; IllegalArgumentException is thrown). There are just a handful of types which can be conjed onto maps, namely two-element vectors, clojure.lang.MapEntrys (which are basically equivalent to two-element vectors) and maps.
Note that seq of a map comprises a bunch of MapEntrys. Thus you can do e.g.
(into a-map (filter a-predicate another-map))
(note that into uses conj -- or conj!, when possible -- internally). Neither merge nor assoc allows you to do that.
merge:
This is almost exactly equivalent to conj, but it replaces its nil arguments with {} -- empty hash maps -- and thus will return a map when the first "map" in the chain happens to be nil.
(apply conj [nil {:a 1} {:b 2}])
; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList
(apply merge [nil {:a 1} {:b 2}])
; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap
Note there's nothing (except the docstring...) to stop the programmer from using merge with other collection types. If one does that, weirdness ensues; not recommended.
assoc:
Again, the example from the question text -- (assoc {:a 1 :b 2} {:c 3}) -- won't work; instead, it'll throw an IllegalArgumentException. assoc takes a map argument followed by an even number of arguments -- those in odd positions (let's say the map is at position 0) are keys, those at even positions are values. I find that I assoc things onto maps more often than I conj, though when I conj, assoc would feel cumbersome. ;-)
merge-with:
For the sake of completeness, this is the final basic function dealing with maps. I find it extremely useful. It works as the docstring indicates; here's an example:
(merge-with + {:a 1} {:a 3} {:a 5})
; => {:a 9}
Note that if a map contains a "new" key, which hasn't occured in any of the maps to the left of it, the merging function will not be called. This is occasionally frustrating, but in 1.2 a clever reify can provide a map with non-nil "default values".
Since maps are such a ubiquitous data structure in Clojure, it makes sense to have multiple tools for manipulating them. The various different functions are all syntactically convenient in slightly different circumstances.
My personal take on the specific functions you mention :
I use assoc to add a single value to a map given a key and value
I use merge to combine two maps or add multiple new entries at once
I don't generally use conj with maps at all as I associate it mentally with lists