LocustHorde
LocustHorde

Reputation: 6399

How to iterate and merge a function results in clojure?

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?

Upvotes: 1

Views: 273

Answers (1)

Magos
Magos

Reputation: 3014

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)) 

Upvotes: 1

Related Questions