I am learning clojure and trying to implement a problem. I am storing maps in a vector. Each map contains an id. For example [{:id 1 :name "abc"} {:id 2 :name "xyz"}]. The map also contains some more fields.
I read somewhere that, instead of using a vector to store the maps, I could use an array-map and do away with my id and store it something like {1 {:name "abc"}, 2 {:name "xyz"}}.
I tried going through the clojure docs but didn't find a good example to achieve this. Can some please help me out and give me a good example?
You can use assoc to add values to a map. assoc takes 3 args. The first arg is the map that you want to add to, 2nd arg is a key, and the third is a value. The function returns the old map with the key-value pair added.
Example:
(assoc {} 1 {:name "abc"})
returns
{1 {:name "abc"}}
Your idea is to lift the :id entry of each record-map into an index, while removing it from the map. You end up with a map of :id-less records instead of a vector of full records.
The following function lifts the key fk out of the collection of maps ms:
(defn key-by [fk ms]
(into {} (map (fn [m] [(get m fk) (dissoc m fk)]) ms)))
For example,
(key-by :id [{:id 1 :name "abc"} {:id 2 :name "xyz"}])
;{1 {:name "abc"}, 2 {:name "xyz"}}
Note:
Every record should have an :id.
Your :ids had better be distinct, or you'll lose records.
Don't depend on array-map: it's an implementation detail. A
modified version might well be a hash-map.
If you need your map sorted by key, use a sorted-map.
If you need to keep your records in insertion order, think again.
Related
(def p {:name "James" :age 26})
I'm trying update method, like
(update p :name "David")
which does not work since the second argument has to be a function.
Try this:
(assoc p :name "David")
Please see this list of documentation, especially the Clojure CheatSheet! See also assoc-in and update-in as described under
Collections -> Maps
P.S. What you have there is a Clojure map value, which is different than an object in JavaScript or a JSON string.
After making a select supposed, the clojure.java.jdbc returns me something like this:
({:name "John"} {:name "Julia"} {:name "Alex"})
I need to transform this return into a vector, to look like this:
["John" "Julia" "Alex"]
How can I do this? I can't think of a way to make this transformation
To get the data in the exact shape you described:
You can use keywords as functions to extract that exact value from a map
You can use the function mapv to call a function on each element of a sequence and get a vector back (eagerly)
E.g.
user=> (mapv :name '({:name "John"} {:name "Julia"} {:name "Alex"}))
["John" "Julia" "Alex"]
(def data [{:name "John"} {:name "Julia"} {:name "Alex"}])
(mapv :name data)
;=> ["John" "Julia" "Alex"]
Please see also this list of documentation resources.
I have a map where each key has a collection as value:
{:name ["Wut1" "Wut2"] :desc ["But1" "But2"]}
The value collections can be assumed to have the same number of elements.
How to transform it into a list (or vector) where each element is a map with key being the key from original collection and value being 1 value like:
[{:name "Wut1" :desc "But1"} {:name "Wut2" :desc "But2"}]
It should be said that the number of keys is not known before (so I can't hardcode for :name and :desc)
(fn [m]
(let [ks (keys m)]
(apply map (fn [& attrs]
(zipmap ks attrs))
(vals m))))
Get the list of keys to use to label everything with
Use apply map to "transpose" the list-of-lists in (vals m) from "for each attribute, a list of the values it should have" to "for each object, a list of the attributes it should have".
zipmap that back together with the keys extracted in the first part.
As a general rule apply map vector will always do a transpose operation:
(apply map vector '(["Wut1" "Wut2"] ["But1" "But2"]))
;;=> (["Wut1" "But1"] ["Wut2" "But2"])
With that one trick in hand, we just need to make sure to zipmap each vector object (e.g. ["Wut1" "But1"]) with the keys:
(fn [m]
(->> m
vals
(apply map vector)
(map #(zipmap (keys m) %))))
;;=> ({:name "Wut1", :desc "But1"} {:name "Wut2", :desc "But2"})
Another rule to go by is that there's a problem when you have consecutive maps - really you ought to bring together the map functions, rather than needlessly transferring items from list to list. (This can often be done using comp). See #amalloy's solution for how to avoid double mapping. Also of course the keys function call should not be done repeatedly as here.
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}} ])
I'm reading data from a file where each line has two values. Each line is represented by a sequence within an outer sequence representing the file.
I'd like to restructure the data into a sequence of maps for further processing.
I know how to create a map from a key set and sequence of values:
=> (defstruct entry :name :age)
=> (apply struct entry '("John" 34))
{:name "John", :age 34}
But how do I create a sequence of such maps based on a sequence of value sequences?
(map (apply struct entry) '(("John" 34) ("Lisa" 41))))
results in:
java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.PersistentStructMap$Def
EDIT: Renamed symbol for clarity.
structs are obsolete, the preference is to use records now.
(defrecord Person [name age])
(map (partial apply ->Person) '(("John" 34) ("Lisa" 41)))
Use zipmap
(map (partial zipmap [:name :age]) '(("John" 34) ("Lisa" 41)))
;-> ({:name "John", :age 34} {:name "Lisa", :age 5})