I have a function that queries my database for the X most recent entries, and it returns a vector of maps along the lines of:
[{:itemID "item1"
:category "stuff"
:price 5}
{:itemID "item2"
:category "stuff"
:price 54}
{:itemID "item3"
:category "stuff"
:price 435}
{:itemID "item4"
:category "otherstuff"
:price 32}]
How I go about destructuring a vector of maps(or is there a better method?) so that I can bind each value into a symbol along lines of:
item-1-id
item-1-category
item-1-cost
item-2-id
item-2-category
item-2-price
...etc
Having trouble grokking this, I get how to destructure a vector, or maps individually, but not a vector of maps, appreciate any help or insight.
That's simply impossible, since destructuring creates local bindings whose names must be known statically.
That's unless the total number of maps is known ahead of time, in which case you could of course write
(let [[{item-1-id :itemID ...} {item-2-id :itemID} ...] ...] ...)
The pattern could be captured in a macro, but the result would likely not be very pretty. (For example, introducing implicit bindings is not very pretty.)
A better solution might be to collect the various values in separate vectors:
(let [vector-of-maps (get-the-vector-of-maps)
ids (mapv :itemID vector-of-maps) ;; note the mapv
categories (mapv :category vector-of-maps)
...]
...)
Then you can say (ids 0) to refer to the ID from the first map, (categories 2) to refer to the category from the third map etc.
This works because vectors in Clojure act as functions of indices, returning the associated values (for example, ([:foo :bar] 0) returns :foo).
Or you could simply use vector-of-maps directly with get-in:
;; get ID from first map
(get-in vector-of-maps [0 :itemID])
See also assoc-in and update-in for producing modified versions of nested data structures.
Related
Not sure how to phrase the question, but, I'm just playing around with the twitter api and clojure as a part of my wanting to learn clojure.
I am not sure what the clojure way of approaching this problem
I am trying to get first 5 tweets of all my followers. I can get the list of followers with the api, and I have a list of follower screen_name. Now, I have a function to get latest 5 tweets from a user. In C#, I would just declare a List<object> and add tweets to it inside a for loop. Clojure doesn't quite work that way.. so here's what I'm trying to do:
(defn get-tweets
[follower]
{:text (str "I am " follower)
:favs 0})
(defn get-all-followers-tweets
[]
(let [followers ["a" "b" "c"]
followers-tweets (map #(get-tweets %) followers)]
followers-tweets))
These are just mockups, but, you get the idea. Now, twitter returns something like this: [{:text "ssd" :fav 1} {:text "fed" :fav 2}]
so when I call get-all-followers-tweets, I get this:
(({:text "I am a", :favs 0}
{:text "I am b", :favs 0}
{:text "I am c", :favs 0}))
I don't know why the data is in 2 brackets, and I'm guessing it has something to do with map but, I just need the :text property from all collections.
doing (get response :text) or (get-in response [:text]) returns nil (assume response is the collection)
So, How do I get all the :text from the collection? Am I approaching this right? I tried (doseq [f followers] (get-tweets f)) and for but they seem very unnatural for getting just all the tweets.
What's the ideal clojure way of doing this?
Your get-tweets fn is returning a series of multiple maps, as a vector. You are then mapping that function over your followers, producing a sequence of sequences of maps. That's why there are two brackets - the outer sequence corresponds to the list of followers and each inner sequence is all the tweets from one follower grouped together.
I think the simplest approach if you're fine with discarding the identity of the authors is to use flatten, a function for unravelling nested sequential data structures to get just the items. That will give you just a sequence of maps without any grouping. You can then map :text over them to get just the texts.
e.g.
(defn get-all-followers-tweets
[]
(let [followers ["a" "b" "c"]
followers-tweets (map get-tweets followers)]
(flatten followers-tweets)))
(map :text (get-all-followers-tweets))
Maybe a more general solution is to consider mapcat, which stands for map-then-concat. It's the go-to approach when you have
a series of data items with some sort of internal structure.
that you want to "unpack" so that each produces one or more of the items you actually want.
It does this by mapping the given function over the outer items to produce a bunch of sequences and then concatenates all those sequences into one. But in this case our "unpacking function" is itself map so I don't think this approach is necessarily clearer here. That just makes it a little difficult to keep the different levels in mind:
(mapcat (partial map :text) (get-all-followers-tweets))
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.
How can I find the name "olle" by specifying :id and value of :id in
(def persons #{{:id 1 :name "olle"}
{:id 2 :name "anna"}})
I need to some function that would get the rows that match to be able to access the values and the keys.
I'm trying to implement SQL syntax in clojure, this is for a school assignment, so I don't need code, more like hints on how I should go about it. So far I know that the functions get-in and clojure.set/select could be used.
Ultimately, what I'm trying to achieve is this statement to be parsed and interpreted
select [name]
from persons
where id=1
->"olle"
This is what I've been testing with so far with no result
(persons :id 1 :name)
;=> :name "olle"
=> (clojure.set/select :id #{1})
#{}
=> (clojure.set/select odd? #{1})
#{1}
I've also played around with get-in, but still I have not managed to get the feel of moving around in the set and hash-maps within the set, so much that I can carry on with the coding, also I'm not sure if I need to define any grammar looking code in this assignment or if I can just do with writing algorithms?
First off, each definition is not a list, they are each a set where each element is a hash-map. Since this is an assignment, I won't provide the complete code, but I will say that your answer could make good use of group-by, get-in, and update-in.
Hint:
(clojure.set/select #(= 1 (:id %)) persons)
;=> #{{:name "olle", :id 1}}
(clojure.set/project *1 [:name])
;=> #{{:name "olle"}}
Presumably your assignment is asking you to come up with a macro transformation from a SQL-like DSL. So, you have two parts to figure out - First, how do I do this with normal functions? Second, how do I effect the transformation with macros?
I'm running two select statements against Cassandra, so instead of having a join I need to join them in code. Being relatively new to Clojure, I'm having a hard time doing this without resorting to really ugly nested loops. Furthermore, if table-b is missing a matching entry from table-a, it should add default table-b values.
The two selects each result in a list of maps (each "row" is one map). The id key is a UUID, not string.
Here's how the selects look if I def something with the same structure.
(def table-a (list {:id "105421db-eca4-4500-9a2c-08f1e09a35ca" :col-b "b-one"}
{:id "768af3f3-3981-4e3f-a93d-9758cd53a056" :col-b "b-two"}))
(def table-b (list {:id "105421db-eca4-4500-9a2c-08f1e09a35ca" :col-c "c-one"}))
I want the end result to be this:
({:id "105421db-eca4-4500-9a2c-08f1e09a35ca" :col-b "b-one" :col-c "c-one"}
{:id "768af3f3-3981-4e3f-a93d-9758cd53a056" :col-b "b-two" :col-c "default-value"})
Thanks for any help.
This can be done by splitting it into groups with the same key, merging all the like-keyed maps and then filling in the default values:
user> (->> (concat table-a table-b) ;; stat with all the data
(sort-by :id) ;; split it into groups
(partition-by :id) ;; by id
(map (partial apply merge)) ;; merge each group into a single map.
(map #(assoc % ;; fill in the missing default values.
:col-c (or (:col-c %) "default value")
:col-b (or (:col-b %) "default value"))))
({:col-c "c-one",
:col-b "b-one",
:id "105421db-eca4-4500-9a2c-08f1e09a35ca"}
{:col-c "default value",
:col-b "b-two",
:id "768af3f3-3981-4e3f-a93d-9758cd53a056"})
Using the thread-last macro ->> makes this a lot easier for me to read, though that is just my opinion. There is also likely a more elegant way to supply the default keys.
First, I assume each structure-specific sequences would have different ways to remove an item: Vectors could be by index, List could be remove first or last, Set should be passing of the actual item to remove, etc.
Second, I assume there are some methods for removal that are structure agnostic; they work on seq interface.
Since sequences are immutable in Clojure, I suspect what you're actually doing is making a cheap copy of the original, only without the original item. This means list comprehension could be used for removal, but I suspect it would be unnecessarily verbose.
Please give some idiomatic examples of the different ways to remove items from Clojure sequences.
There is no single interface for removing things from all of Clojure's data structure types, possibly because of the different performance characteristics.
(disj #{:foo :bar} :foo) ; => #{:bar}
(dissoc {:foo 1 :bar 2} :foo) ; => {:bar 2}
(pop [:bar :foo]) ; => [:bar]
(pop (list :foo :bar)) ; => (:bar)
These also work (returning a seq):
(remove #{:foo} #{:foo :bar}) ; => (:bar)
(remove #{:foo} [:foo :bar]) ; => (:bar)
(remove #{:foo} (list :foo :bar)) ; => (:bar)
This doesn't work for hash-maps because when you iterate over a map, you get key/value pairs. But this works:
(remove (fn [[k v]] (#{:foo} k)) {:foo 1 :bar 2}) ; => ([:bar 2])
Look at the Clojure reference for sequences. filter and remove are what you seek.
As an extension of Brian Carper's answer. It depends on what you will be doing with the result. If you are passing the result to something that wants to work on the entire set of data (ie to print it) It is idiomatic to make a seq and use filter or remove to solve the problem lazily. If on the other hand you are modifying the data structure to save for various later uses then creating a seq on it would loose its favorable update characteristics so in this case its better to use the update function specific to that data structure.