Clojure: Call a function for each element in a vector with it index

20,018

Solution 1

The way you're doing it is idiomatic (and identical to clojure.contrib.seq-utils/indexed in fact). If you really want to avoid the extra data structure, you can do this:

(loop [data data, index 0]
  (when (seq data)
    (setCell 0 index (first data))
    (recur (rest data) (inc index))))

I'd use your version unless there was a good reason not to though.

Solution 2

You can get the same effect in a very clojure-idiomatic way by just mapping the indexes along with the data.

(map #(setCell 0 %1 %2) (iterate inc 0) data)

You may want to wrap this in a (doall or (doseq to make the calls happen now. It's just fine to map an infinite seq along with the finite one because map will stop when the shortest seq runs out.

Solution 3

A bit late in the game but for people accessing this page: there is now (since clojure 1.2) a map-indexed function available in clojure.core.

One issue (unless I'm mistaken): there's no "pmap" equivalent, meaning that map-indexed computations cannot easily be parallelized. In that case, I'd refer to solutions offered above.

Solution 4

The nicest way would be to use clojure.contrib.seq-utils/indexed, which will look like this (using destructuring):

(doseq [[idx val] (indexed ["Hello" "World" "Test" "This"])]
  (setCell 0 idx val))

Solution 5

I did a short comparison of the performance of the options sofar:

; just some function that sums stuff 
(defn testThis
  [i value]
 (def total (+ total i value)))

; our test dataset. Make it non-lazy with doall    
(def testD (doall (range 100000)))

; time using Arthur's suggestion
(def total 0.0)
(time (doall (map #(testThis %1 %2) (iterate inc 0) testD)))
(println "Total: " total)

; time using Brian's recursive version
(def total 0.0)
(time (loop [d testD i 0]
  (when (seq d)
    (testThis i (first d))
    (recur (rest d) (inc i)))))
(println "Total: " total)

; with the idiomatic indexed version
(def total 0.0)
(time (let [idv (map vector (iterate inc 0) testD)]
  (doseq [[i value] idv] (testThis i value))))
(println "Total: " total)

Results on my 1 core laptop:

   "Elapsed time: 598.224635 msecs"
   Total:  9.9999E9
   "Elapsed time: 241.573161 msecs"
   Total:  9.9999E9
   "Elapsed time: 959.050662 msecs"
   Total:  9.9999E9

Preliminary Conclusion:

Use the loop/recur solution.

Share:
20,018
Jeroen Dirks
Author by

Jeroen Dirks

I am an Amazon Sr. SDE working in the Customer Service side of the company.

Updated on January 08, 2020

Comments

  • Jeroen Dirks
    Jeroen Dirks over 4 years

    Say I have a vector:

    (def data ["Hello" "World" "Test" "This"])
    

    And I want to populate a table somewhere that has an api:

    (defn setCell
      [row col value]
      (some code here))
    

    Then what is the best way to get the following calls to happen:

    (setCell 0 0 "Hello")
    (setCell 0 1 "World")
    (setCell 0 2 "Test")
    (setCell 0 3 "This")
    

    I found that the following will work:

    (let [idv (map vector (iterate inc 0) data)]
      (doseq [[index value] idv] (setCell 0 index value)))
    

    But is there a faster way that does not require a new temporary datastructure idv?