pedrohreis
pedrohreis

Reputation: 1090

Reducing a list of maps to a list by in clojure

I've started to get some functional programming some weeks ago and I'm trying to perform a mapping from a list of maps to a list considering a specific key in clojure.

My list of maps looks like: '({:a "a1" :b "b1" :c "c1"} {:a "a2" :b "b2" :c "c2"} {:a "a3" :b "b3" :c "c3"})

And the output I'm trying to get is: '("b1" "b2" "b3").

I've tried the following:

(doseq [m maps]
  (println (list (get m :b))))

And my output is a list of lists (what is expected as I'm creating a list for each iteration). So my question is, how can I reduce this to a single list?

Update

Just tried the following:

(let [x '()]
  (doseq [m map]
    (conj x (get m :b))))

However, it is still not working. I`m not getting the point as I was expecting to be appending the elements into a empty list

Upvotes: 3

Views: 458

Answers (3)

Thumbnail
Thumbnail

Reputation: 13473

(doseq [m maps]
  (println (list (get m :b))))

In two short lines, you break several general rules of functional programming:

  • Pass data into a function as arguments, not as references to global variables.
  • Don't print the results of computation. Return them as the value of the function.
  • Avoid mechanisms such as doseq that work by side-effects.

Despite this, you were not too far from a solution. doseq is essentially a version of for that throws away its result. If we replace doseq with for, and get rid of the println and the list, we get

=> (for [m maps] (get m :b))
("b1" "b2" "b3")

But Arthur Ulfeldt's simple use of map is better.

Upvotes: 0

Alan Thompson
Alan Thompson

Reputation: 29958

You have the right idea, but are using the wrong function. doseq is intended only for side effects and always returns nil. The function you are looking for is for, which takes a sequence as input and returns another sequence as output. I generally prefer for over the similar map as for allows you to name the loop variable:

(def data-list
  [{:a "a1" :b "b1" :c "c1"}
   {:a "a2" :b "b2" :c "c2"}
   {:a "a3" :b "b3" :c "c3"}])

(let [result (vec (for [item data-list]
                     (:b item)))]
  (println result)   ; print result
  result)            ; return result from `let` expression

result => ["b1" "b2" "b3"]

If instead you do this:

(println
  (doseq [item data-list]
    (println (:b item))))

you can see the difference with doseq vs for:

b1    ; loop item #1
b2    ; loop item #2
b3    ; loop item #3
nil   ; return value of doseq

Please see https://www.braveclojure.com/ for online details, and buy a good book (or 5) like Getting Clojure, etc.

Upvotes: 0

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91534

This is a very common pattern in production Clojure code so it's a good place to learn. In general check out the docs on sequences at https://clojure.org/reference/sequences and when faced with similar task, look to see which pattern best fits and explore functions in that group. In this case it's "Process each item of a seq to create a new seq" and the first item listed is map

your example might look like

 (map :b my-data)

Upvotes: 7

Related Questions