Common Lisp中类似于Python的enumerate()函数

7

我想对一个列表进行映射,但在列表中要跟踪元素索引。

在Python中,我可以这样做:

map(lambda (idx, elt): "Elt {0}: {1}".format(idx, elt), enumerate(mylist))

我正在尝试将其翻译为以下内容:

我正在尝试将其翻译为以下内容:

(mapcar-something (lambda (elt idx) (format nil "Elt ~D: ~S" idx elt))
                  '(a b c d))

预期结果:

("Elt 0: A" "Elt 1: B" "Elt 2: C" "Elt 3: D")

但是我找不到我应该使用的mapcar-something函数。我需要自己实现吗(也许通过loop)?


你可以将它包装在一个 let 块中,并从 lambda 中捕获一个索引变量,然后每次调用 lambda 时递增它。我不认为有内置的方法。 - MicroVirus
4个回答

9
CL-USER 25 > (defun iota (n)
               (loop for i below n collect i))
IOTA

CL-USER 26 > (iota 4)
(0 1 2 3)

CL-USER 27 > (mapcar (lambda (elt idx)
                       (format nil "Elt ~D: ~S" idx elt))
                     '(a b c d)
                     (iota 4))
("Elt 0: A" "Elt 1: B" "Elt 2: C" "Elt 3: D")

或者

CL-USER 28 > (loop for elt in '(a b c d) and idx from 0
                   collect (format nil "Elt ~D: ~S" idx elt))
("Elt 0: A" "Elt 1: B" "Elt 2: C" "Elt 3: D")

1
我不知道 and idx from 0 会自动将 idx 每次增加一。 - ssice

5

Common Lisp的LOOP宏可以自动跟踪索引。

(loop for elt in '(a b c d) and idx from 0
      collect (operation-on idx elt))

LOOP会自动将使用from初始化的变量自增1;如果它们是由and引入的,则两个赋值(数组元素和索引)同时发生,而不是嵌套。

因此,类似于enumerate函数的示例代码如下:

(defun enumerate (list &optional (first-index 0))
  (loop for elt in list and idx from first-index
    collect (cons idx elt)))

在Common Lisp的表现力方面,定义一个类似以下的宏可能会很有用:
(defmacro with-enumerated-list ((list elt idx &key (first-index 0)) &body body)
  `(loop for ,elt in ,list and ,idx from ,first-index
     collect (progn ,@body)))

在这种情况下,enumerate函数可简化为:
(defun enumerate (list &optional (first-index 0))
  (with-enumerated-list (list elt idx :first-index first-index)
    (cons idx elt)))

1
典型问题:您尚未尝试自己的代码。第二个ENUMERATE函数无法正常工作。不需要宏,您的宏接口很丑陋,没有错误检查,并且与普通的LOOP相比没有提供任何附加值。更糟糕的是:您定义主体的方式会引起许多问题(您能看出来吗)?通过引入宏,您引入了一个错误和另一个随机错误的来源... - Rainer Joswig
@RainerJoswig 这个回答真的那么糟糕吗?难道下投票的价值比改进它更大吗?我试着写了自己的代码,但似乎我复制了一个错误版本。它提供的价值是与语言相关的惯用语,这可能并没有多大意义。如果你认为这个宏丑陋是因为缺乏卫生性,那可能既有好处也有坏处。您为什么这么强烈地持负面意见? - ssice
LOOP语法泄漏到WITH-ENUMERATED-LIST中,你看到了吗?如果没有,我会在我的下一个评论中告诉你。 - Rainer Joswig
所以,你能解释一下为什么这个不起作用吗:(with-enumerated-list ('(1 2 3 4 5) it count) (print it) (print count) count) - Rainer Joswig
@RainerJoswig 我明白了,感谢您的“错误报告”,我刚刚编辑了答案。而且我们可能也想将collect参数化(这样我们只需要执行操作),甚至作为第一个索引的关键字参数,但我认为这更适合作为GitHub上要点的后续内容,而不是在此处添加。还有其他什么问题吗? - ssice

2
这是另一个例子:
(defun enumerate (collection &key (as 'list))
  (let ((index -1))
    (map as
      (lambda (element)
        (cons (incf index) element))
      collection)))

这个方法的优势在于它适用于列表和向量,并且可以产生两者的结果:
CL-USER[2]: (enumerate '(1 2 3))
((0 . 1) (1 . 2) (2 . 3))

CL-USER[3]: (enumerate "abc" :as 'vector)
#((0 . #\a) (1 . #\b) (2 . #\c))

1
如果您想要的是类似于您原始示例的东西:
(defun enumerate (function list)
  (let ((idx 0))
    (loop for elt in list
      collect (funcall function elt idx)
      do (incf idx))))

你的例子:
(enumerate (lambda (elt idx) (format nil "Elt ~D: ~S" idx elt))
           '(a b c d))
=> ("Elt 0: A" "Elt 1: B" "Elt 2: C" "Elt 3: D")

没问题,但是Rainer的第二个代码示例通过处理循环实现中的incf来改进你的代码,从而将其隐藏在程序员之外,这更接近Python中的用法。 - ssice

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