在Clojure中枚举一个序列?

27

在Python中我可以这样做:

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

输出结果为:

0 dog
1 cat
2 bird

我该如何在Clojure中实现同样的功能?我考虑使用类似于以下代码的列表推导:

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

但是这会打印出每个数字和动物的组合。我猜肯定有一种简单而优雅的方法来做到这一点,但我没想到。

5个回答

43

从1.2版本开始,核心库中有map-indexed函数。

你的示例代码如下:

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

9

快速解决方案:

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

或者,如果你想将它包装在一个函数中:

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

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

这里发生的是将函数向量应用于两个序列中的每个元素,并将结果收集到一个惰性集合中。
请在您的repl中尝试。

8

使用来自clojure.contrib.seq的索引:

用法:(indexed s) 返回一个惰性序列,其中包含[index, item]对,其中item来自's',而索引从零开始计数。

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

对于您的示例,这是

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

嘿。请查看函数indexed的源代码:https://github.com/clojure/clojure-contrib/blob/b8d2743d3a89e13fc9deb2844ca2167b34aaa9b6/src/main/clojure/clojure/contrib/seq.clj#L51 - Leonel
哈哈,我知道。不过我想知道为什么你在示例中把函数命名为“enum”呢 :-) - ordnungswidrig
1
clojure-contrib现已被弃用。 - Indra

5

map-indexed 看起来没问题,但是:我们真的需要其他答案中的所有 doseq 和解构参数吗?

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

编辑: 如@gits所指出的,在REPL中可以工作,但它不遵守clojure默认的惰性计算。 dorun 似乎是最接近 doseq, doalldorun之间的区别。然而, doseq 在这里似乎是惯用的首选。

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

2
map-indexed 返回一个惰性序列。doseq 是必要的,以确保副作用现在发生,而不是以后(在这种情况下,是由 println 打印的效果)。 - glts
@Git 对了,谢谢。我注意到了,但是我还在学习中,不确定 doseq 是否是这里真正喜欢的惯用语。 - Phil Cooper

1
另一个选择是使用reduce-kv,它将向量的元素与它们的索引配对。

因此,

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

或许稍微更明确的是:

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

我不会在这里选择这个解决方案,而是选择使用 doseq,但了解向量在 reduce-kv 中的特殊化还是很好的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接