您的函数有效,但依赖于APPLY。
您可以传递给apply 的列表数量受CALL-ARGUMENTS-LIMIT 的限制,这在给定的实现中可能足够大。但是,您的函数应该接受任意数量的列表,因此,如果您想以可移植方式编写代码,则不应使用 apply。
压缩
zip 函数组合了列表列表中的所有第一个元素,然后是所有第二个元素等。
如果您输入的列表列表如下:
'((a b c d)
(0 1 2 3)
(x y z t))
然后 zip 将构建以下列表:
(list (combine (list a 0 x))
(combine (list b 1 y))
(combine (list c 2 z))
(combine (list d 3 t)))
如果您仔细观察,您会发现您可以将正在完成的工作拆分为combine 函数的映射,而不是初始列表列表的转置 (视为矩阵):
((a b c d) ((a 0 x)
(0 1 2 3) ===> (b 1 y)
(x y z t)) (c 2 z)
(d 3 t))
因此,zip 可以定义为:
(defun zip (combine lists)
(mapcar #'combine (transpose lists)))
或者,如果您关心重用调用transpose 时分配的内存,您可以使用MAP-INTO:
(defun zip (combiner lists &aux (transposed (transpose lists)))
(map-into transposed combiner transposed))
转置
转置列表列表的方法有多种。这里我将使用REDUCE,在其他一些函数式语言中称为fold。在归约的每一步,中间归约函数都会取一个部分结果和一个列表,并产生一个新的结果。我们的结果也是一个列表列表。
下面,我展示了每个减少步骤的当前列表,以及列表的结果列表。
第一步
(a b c d) => ((a)
(b)
(c)
(d))
第二步
(0 1 2 3) => ((0 a)
(1 b)
(2 c)
(3 d))
第三步
(x y z t) => ((x 0 a)
(y 1 b)
(z 2 c)
(t 3 d))
请注意,元素被推到每个列表的前面,这就解释了为什么每个结果列表都被颠倒了。预期的结果。
因此,transpose 函数应该在执行归约后反转每个列表:
(defun transpose (lists)
(mapcar #'reverse
(reduce (lambda (result list)
(if result
(mapcar #'cons list result)
(mapcar #'list list)))
lists
:initial-value nil)))
像以前一样,最好避免分配过多的内存。
(defun transpose (lists)
(let ((reduced (reduce (lambda (result list)
(if result
(mapcar #'cons list result)
(mapcar #'list list)))
lists
:initial-value nil)))
(map-into reduced #'nreverse reduced)))
示例
(transpose '(("ab" "cd" "ef") ("01" "23" "45")))
=> (("ab" "01") ("cd" "23") ("ef" "45"))
(import 'alexandria:curry)
(zip (curry #'join-string "|")
'(("ab" "cd" "ef") ("01" "23" "45")))
=> ("ab|01" "cd|23" "ef|45")
编辑:其他答案已经提供了join-string 的示例定义。您还可以使用cl-strings 中的join 函数。