geodude
geodude

Reputation: 231

Clojure - using map recursively

If I have a list, I can use map to apply a function to each item of the list.

(map sqrt (list 1 4 9))

(1 2 3)

I can also use map in front of a list of lists:

(map count (list (list 1 2 3) (list 4 5)))

(4 5)

Now is there a way to apply sqrt to each number in the list of lists? I want to start from

(list (list 1 4 9) (list 16 25))

and obtain

((1 2 3)(4 5))

However, the following does not seem to work,

(map (map sqrt) (list (list 1 4 9) (list 16 25)))

nor the following.

(map (fn [x] (map sqrt x)) (list (list 1 4 9) (list 16 25)))

Why? (And how do I solve this?)

Upvotes: 1

Views: 336

Answers (4)

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 10010

@MartinPuda's answer is right.

The tail call recursive version is here:

(defn map* [f sq & {:keys [acc] :or {acc '()}}]
  (cond (empty? sq) (vec (reverse acc))
        (sequential? (first sq)) (map* f 
                                       (rest sq)
                                       :acc (cons (map* f (first sq)) acc))
        :else (map* f (rest sq) :acc (cons (f (first sq)) acc))))

By tradition in lisp, such recursively into the nested structure going functions are fnname* (marked by an asterisk at the end). acc accumulates the result nested tree which is constructed by cons.

In your case this would be:

(map* Math/sqrt (list (list 1 4 9) (list 16 25)))

Test with:

(map* (partial + 1) '[1 2 [3 4 [5] 6] 7 [8 [9]]])

;; => [2 3 [4 5 [6] 7] 8 [9 [10]]]

Upvotes: 1

cfrick
cfrick

Reputation: 37073

Your second to last version "nearly" works. Clojure has no automatic currying, so (map sqrt) is not partial application, but (map sqrt) returns a transducer, which takes one argument and returns a function with three different arities - so running your code there will give you back a function for each list of numbers.

To make that work, you can use partial:

user=> (map (partial map sqrt) (list (list 1 4 9) (list 16 25)))
((1 2 3) (4 5))

And of course there is the obligatory specter answer:

user=> (transform [ALL ALL] sqrt '((1 4 9)(16 25)))
((1 2 3) (4 5))

Upvotes: 3

Martin Půda
Martin Půda

Reputation: 7576

You can write recursive function for this task:

(defn deep-map [f seq1]
  (cond (empty? seq1) nil
        (sequential? (first seq1))
        (cons (deep-map f (first seq1))
              (deep-map f (rest seq1)))
        :else (cons (f (first seq1))
                    (deep-map f (rest seq1)))))

Example:

(deep-map #(Math/sqrt %) '((1 4 9) (16 25 36)))
=> ((1.0 2.0 3.0) (4.0 5.0 6.0))

Or you can use clojure.walk and function postwalk:

(clojure.walk/postwalk
  #(if (number? %) (Math/sqrt %) %)
  '((1 4 9) (16 25 36)))

=> ((1.0 2.0 3.0) (4.0 5.0 6.0))

Upvotes: 3

Alan Thompson
Alan Thompson

Reputation: 29984

The function map is closely related to the function for, which I think is sometimes easier to use. Here is how I would solve this problem:

  (let [matrix [[1 4 9]
                [16 25]]
        result (vec (for [row matrix]
                      (vec (for [num row]
                             (Math/sqrt num)))))]
    result)

with result:

result => 
    [[1.0 2.0 3.0] 
     [4.0 5.0]]

If you remove the two (vec ...) bits, you'll see the same result but for normally returns a lazy sequence.

Upvotes: 1

Related Questions