【问题标题】:Create consecutive sublists of given length from a list in LISP从 LISP 中的列表创建给定长度的连续子列表
【发布时间】:2021-08-07 16:20:15
【问题描述】:

我有一个列表(1 2 3 4 5 6),例如,我想创建所有可能的 4 个原子列表。结果将是((1 2 3 4)(2 3 4 5)(3 4 5 6))

这在 Lisp 中可行吗?谢谢。

【问题讨论】:

标签: list lisp common-lisp sublist


【解决方案1】:
(defun slide (list n)
  (loop for head on list
        for tail = (nthcdr n list) then (cdr tail)
        collect (ldiff head tail)
        while tail))

测试:

[1]> (slide nil 0) 
NIL
[2]> (slide nil 1)
NIL
[3]> (slide nil 2)
NIL
[4]> (slide '(1) 0)
(NIL)
[5]> (slide '(1) 1)
((1))
[6]> (slide '(1) 2)
((1))
[7]> (slide '(1) 3)
((1))
[8]> (slide '(1 2 3) 0)
(NIL NIL NIL)
[9]> (slide '(1 2 3) 1)
((1) (2) (3))
[10]> (slide '(1 2 3) 2)
((1 2) (2 3))
[11]> (slide '(1 2 3) 3)
((1 2 3))
[12]> (slide '(1 2 3) 4)
((1 2 3))
[13]> (slide '(1 2 3) 5)
((1 2 3))
[14]> (slide '(1 2 3 4 5) 3)
((1 2 3) (2 3 4) (3 4 5))
[15]> (slide '(1 2 3 4 5) 4)
((1 2 3 4) (2 3 4 5))
[16]> (slide '(1 2 3 4 5) 5)
((1 2 3 4 5))
[17]> (slide '(1 2 3 4 5) 6)

n 超过列表长度或为零的测试用例超出规范;包括那些只是为了显示实际行为。值 -1 将违反 nthcdr 的约定,因此应发出错误信号,除非编译为低安全性。

它是如何工作的:

  • for head on list 中的on 迭代列表的cons 单元主干,而不是项目。 head 循环变量在第一次迭代时设置为 list。然后在第二次迭代中它转到(cdr head)。这将在nil 被命中时终止。

  • 与此迭代并行,我们有for tail = (nthcdr n list) then (cdr tail)。这与for tail on (nthcdr n list) 非常相似。 tail 变量被初始化为列表下方的n 单元格,然后通过cdr 前进。我们不使用for var on list 子句的原因是我们不想在循环变量命中nil 时终止。当tail 变为nil 时,我们希望将nil 值包含在迭代中,因此我们使用loopfor var = initial-val then step-val 子句打开所需的迭代代码。

  • 因此我们有headtail 指针在列表中以彼此偏移的方式行进,如图所示,显示列表(1 2 3 4) 以及headtail 与@ 的初始状态987654349@是2:

      [ 1 | * ] <--- head
            |
            v
          [ 2 | * ]
                |
                v
              [ 3  | * ] <---  tail
                     |
                     v
                   [ 4 | NIL ] 
    

    所以在这里,在第一次迭代中,我们将计算 (ldiff head tail)。返回的是head 列表前缀的副本,没有tail 部分。 ldiff 向下移动 head 列表,将项目复制到新列表中,并在遇到 tail 指针时停止复制。如果您理解git,那么(ldiff head tail) 类似于git cherry-pick tail..head。来自head 的项目被挑选到返回列表中,直到tail,这不是挑选的。在此示例中,(ldiff head tail) 产生 (1 2)tail 指向(3 4),不包括在内。

  • 在每次迭代中,headtail 都在列表中并行向下移动一个单元格,因此 ldiff 将继续计算列表中不同的 n 大小的切片。下次我们会收集(2 3)

  • 在最后一次迭代中,tail 变为 nil。在这种情况下,(ldiff head tail) 的行为就像(copy-list head):它不断地将项目复制到一个新列表中,直到遇到tail。因为那是nil,所以当它到达列表的末尾时会发生这种情况,此时它已经复制了所有的列表。

  • while tail 确保终止。这个子句放在底部,这样我们就有了一个底部测试:我们想在tailnil 时包含迭代,然后停止。

另一方面,这里有一个 TXR Lisp 语言的解决方案,它展示了如何通过模式匹配解决这个问题。这很容易被转录以与其他 Lisp 方言及其可用的模式匹配库(如 Common Lisp 的 Trivia)一起使用。

1> (append-each ((tail (conses '(1 2 3 4 5 6 7))))
      (if-match (@a @b @c @d . @nil) tail
         ^((,a ,b ,c ,d))))
((1 2 3 4) (2 3 4 5) (3 4 5 6) (4 5 6 7))

唯一的问题是,我们对参数 n 进行了硬编码,因为它是由模式的长度表示的,这是代码中的一段语法。

在某些情况下,我更喜欢这种解决方案,尤其是当需求以某种方式发生变化时。例如,假设我们只需要收集那些满足某些算术属性的四联体。能够通过abcd 变量名来引用元组元素会很方便。

我们可以在模式本身中引入约束。例如:只收集以 1 开头的元组:

5> (append-each ((tail (conses '(1 2 1 2 3 1 2 3 4 5 6 1 2 3 4))))
      (if-match (1 @b @c @d . @nil) tail
        ^((1 ,b ,c ,d))))
((1 2 1 2) (1 2 3 1) (1 2 3 4) (1 2 3 4))

另一种方式,保留变量a

7> (append-each ((tail (conses '(1 2 1 2 3 1 2 3 4 5 6 1 2 3 4))))
      (if-match (@(eql 1 @a) @b @c @d . @nil) tail
        ^((,a ,b ,c ,d))))
((1 2 1 2) (1 2 3 1) (1 2 3 4) (1 2 3 4))

或以奇数结尾的元组:

8> (append-each ((tail (conses '(1 2 1 2 3 1 2 3 4 5 6 1 2 3 4))))
      (if-match (@a @b @c @(oddp @d) . @nil) tail
        ^((,a ,b ,c ,d))))
((2 1 2 3) (1 2 3 1) (3 1 2 3) (2 3 4 5) (4 5 6 1) (6 1 2 3))

或使用保护表达式(通过此模式匹配器中的require 运算符)仅收集最后两个元素相加为五的元组:

9> (append-each ((tail (conses '(1 2 1 2 3 1 2 3 4 5 6 1 2 3 4))))
      (if-match @(require (@a @b @c @d . @nil)
                          (= 5 (+ c d)))
                tail
        ^((,a ,b ,c ,d))))
((2 1 2 3) (3 1 2 3) (6 1 2 3))

【讨论】:

  • 处理了那些边缘情况。 :)(以前从不知道ldiff...)
【解决方案2】:

是的,有可能:

[7]> (mapcar #'list '(1 2 3 4 5 6) '(2 3 4 5 6) '(3 4 5 6) '(4 5 6))
((1 2 3 4) (2 3 4 5) (3 4 5 6))

代码太具体了。写同一件事的更一般的方法是

[9]> (apply #'mapcar (cons #'list 
       '((1 2 3 4 5 6) (2 3 4 5 6) (3 4 5 6) (4 5 6)) ))
((1 2 3 4) (2 3 4 5) (3 4 5 6))

现在这更容易概括。它表明我们需要实现一个像这样工作的函数:

(foo 4 '(1 2 3 4 5 6))
=>
((1 2 3 4 5 6) (2 3 4 5 6) (3 4 5 6) (4 5 6))

那么我们的解决方案是可以实现的

(defun sublists (n lst)
  (apply #'mapcar (cons #'list
     (foo n lst))))

剩下的就是实现这个foo。它需要迭代n 次(或者是n-1?),每次都取rest

[10]> (rest '(1 2 3 4 5 6))
(2 3 4 5 6)
[11]> (rest *)
(3 4 5 6)
[12]> (rest *)
(4 5 6)
[13]> (rest *)
(5 6)
[14]> (rest *)
(6)
[15]> (rest *)
NIL
[16]> (rest *)
NIL

尝试实现它,同时处理所有边缘情况。

【讨论】:

    【解决方案3】:

    另一个,结合循环和地图车应用程序:

    (defun slide (n data)
      (when (>= (length data) n)
        (loop repeat n
              for x on data
              collect x into res
              finally (return (apply #'mapcar #'list res)))))
    
    CL-USER> (slide 4 (list 1 2 3))
    ;;=> NIL
    
    CL-USER> (slide 4 (list 1 2 3 4))
    ;;=> ((1 2 3 4))
    
    CL-USER> (slide 4 (list 1 2 3 4 5 6 7 8))
    ;;=> ((1 2 3 4) (2 3 4 5) (3 4 5 6) (4 5 6 7) (5 6 7 8))
    

    【讨论】:

      【解决方案4】:
      (defun partition (n list &key step)
        "Collects a list of N items from the LIST, moving STEP times forward in each iteration"
        (labels ((take (m list)
                   (when (<= m (length list))
                     (loop for i from 0 to (decf m)
                           collect (nth i list)))))
          (unless step
            (setf step n))
          (assert (> step 0))
          (let ((p (take n list)))
            (when (= n (length p))
              (cons p (partition n (nthcdr step list) :step step))))))
      
      ;;; Your example
      
      CL-USER> (partition 4 '(1 2 3 4 5 6) :step 1)
      ((1 2 3 4) (2 3 4 5) (3 4 5 6))
      

      更多示例:

      CL-USER> (partition 3 '(1 2 3 4 5 6))
      ((1 2 3) (4 5 6))
      CL-USER> (partition 2 '(1 2 3 4 5 6) :step 1)
      ((1 2) (2 3) (3 4) (4 5) (5 6))
      CL-USER> (partition 2 '(1 2 3 4 5 6) :step 2)
      ((1 2) (3 4) (5 6))
      CL-USER> (partition 1 '(1 2 3 4 5 6) :step 2)
      ((1) (3) (5))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-04-11
        • 1970-01-01
        • 1970-01-01
        • 2021-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多