如何将列表转换为子列表?

How to transform list into sub lists?

((1 2 3)
(2 3 4)
(3 4 5)
(4 5 6))

来自

(1 2 3 4 5 6)

这种操作的类型是什么?

我尝试了什么:

(loop
   :with l2 = '()
   :with l1 = '(1 2 3 4 5 6)
   :for i :in l1
   :do (push (subseq l1 0 3) l2))

您每次循环都推送相同的子列表。

您可以使用 :for sublist on 循环列表的连续尾部。

并使用 :collect 列出所有结果,而不是推入您自己的列表

(loop
   :for l1 on '(1 2 3 4 5 6)
   :if (>= (length l1) 3)
      :collect (subseq l1 0 3)
   :else 
      :do (loop-finish))

或者使用 map:

(let ((l '(1 2 3 4 5 6)))
  (map 'list #'list l (cdr l) (cddr l)))

;; ((1 2 3) (2 3 4) (3 4 5) (4 5 6))

您可以将其解读为:

  • 列表 l 的值为 (1 2 3 4 5 6)
  • map 遍历列表及其两个连续的 cdrs
  • 通过对列表的元素应用 #'list map 并行循环
  • (最短列表用完停止)
  • 并收集结果 as/into a 'list

@WillNess 建议更简单:

(let ((l '(1 2 3 4 5 6)))
  (mapcar #'list l (cdr l) (cddr l)))

谢谢!那么我们可以仅使用 map 变体进行概括:

(defun subseqs-of-n (l n)
  (apply #'mapcar #'list (subseq (maplist #'identity l) 0 n)))

(maplist #'identity l) 等价于 (loop for sl on l collect sl)。 然而,

(loop for sl on l
      for i from 0 to n
      collect sl)

更好,因为它在第 n 轮循环时停止...

这是 Barmar 答案的一个版本(应该是公认的),它更通用一些,只调用 length 一次。

(defun successive-leading-parts (l n)
  (loop repeat (1+ (- (length l) n))
        for lt on l
        collect (subseq lt 0 n)))
> (successive-leading-parts '(1 2 3 4) 3)
((1 2 3) (2 3 4))

> (successive-leading-parts '(1 2 3 4) 2)
((1 2) (2 3) (3 4))

首先让我们定义一个函数take-n,如果没有足够的项目,它要么是returns n 个项目,要么是一个空列表。它不会扫描整个列表。

(defun take-n (n list)
  (loop repeat n
        when (null list) return (values nil nil)
        collect (pop list)))

然后我们将此函数 take-n 移动到列表中,直到 returns NIL.

(defun moving-slice (n list)
  (loop for l on list
        for p = (take-n n l)
        while p
        collect p))

示例:

CL-USER 207 > (moving-slice 3 '(1 2))
NIL

CL-USER 208 > (moving-slice 3 '(1 2 3))
((1 2 3))

CL-USER 209 > (moving-slice 3 '(1 2 3 4 5 6 7))
((1 2 3) (2 3 4) (3 4 5) (4 5 6) (5 6 7))

或者更经典的C-like for-loop-ing with indexes来解决它。 但是在strings/vectors上用的多,在列表上用的少,因为它的性能是

  • 对于二次列表
  • 用于向量(字符串!)线性,因此最好与它们一起使用!

感谢 @WillNess 指出了这两点(见下面的评论)。

(defun subseqs-of-n (ls n) ;; works on strings, too!
  (loop :for i :from 0 :to (- (length ls) n)
        :collect (subseq ls i (+ i n))))

所以 vectors/strings 使用:

(subseqs-of-n "gattaca" 5)
;; ("gatta" "attac" "ttaca")