dvcolgan
dvcolgan

Reputation: 7728

Enumerate over a sequence in Clojure?

In Python I can do this:

animals = ['dog', 'cat', 'bird']
for i, animal in enumerate(animals):
    print i, animal

Which outputs:

0 dog
1 cat
2 bird

How would I accomplish the same thing in Clojure? I considered using a list comprehension like this:

(println
  (let [animals ["dog" "cat" "bird"]]
    (for [i (range (count animals))
          animal animals]
      (format "%d %d\n" i animal))))

But this prints out every combination of number and animal. I'm guessing there is a simple and elegant way to do this but I'm not seeing it.

Upvotes: 27

Views: 9633

Answers (5)

glts
glts

Reputation: 22684

Yet another option is to use reduce-kv, which pairs the elements of a vector with their indexes.

Thus,

(reduce-kv #(println %2 %3) nil ["dog" "cat" "bird"])

or perhaps the slightly more explicit

(reduce-kv (fn [_ i animal] (println i animal)) nil ["dog" "cat" "bird"])

I wouldn’t pick this solution over the one with doseq here, but it is good to be aware of this specialisation for vectors in reduce-kv.

Upvotes: 1

Phil Cooper
Phil Cooper

Reputation: 5877

map-indexed looks right but: Do we really need all of the doseq and deconstructing args stuff in the other answers?

(map-indexed println ["dog", "cat", "bird"])

EDIT: as noted by @gits this works in the REPL but doesn't respect that clojure is lazy by default. dorun seems to be the closest among doseq, doall and doseq for this. doseq, howver, seems to be the idiomatic favorite here.

(dorun (map-indexed println ["dog", "cat", "bird"]))

Upvotes: 5

kotarak
kotarak

Reputation: 17299

There is map-indexed in core as of 1.2.

Your example would be:

(doseq [[i animal] (map-indexed vector ["dog" "cat" "bird"])]
  (println i animal))

Upvotes: 46

ordnungswidrig
ordnungswidrig

Reputation: 3176

Use indexed from clojure.contrib.seq:

Usage: (indexed s) Returns a lazy sequence of [index, item] pairs, where items come from 's' and indexes count up from zero.

(indexed '(a b c d)) => ([0 a] [1 b] [2 c] [3 d]

For your example this is

(require 'clojure.contrib.seq)
(doseq [[i animal] (clojure.contrib.seq/indexed ["dog", "cat", "bird"])]
  (println i animal))

Upvotes: 8

Leonel
Leonel

Reputation: 29189

Quick solution:

(let [animals ["dog", "cat", "bird"]]
  (map vector (range) animals))

Or, if you want to wrap it in a function:

(defn enum [s]
  (map vector (range) s))

(doseq [[i animal] (enum ["dog", "cat", "bird"])]
  (println i animal))

What happens here is the function vector is applied to each element in both sequences, and the result is collected in a lazy collection.

Go ahead, try it in your repl.

Upvotes: 9

Related Questions