带列表的Lisp递归

时间:2012-09-03 04:51:31

标签: lisp common-lisp

我需要一个函数,它将带有单词的列表,并将该列表拆分为两个列表,如果在任何时候找到单词'FOO'。我想出了一个递归的解决方案,可能不是最好的,但我遇到了一些麻烦。我只需要传递1个参数,即要分析的列表,但我不知道如何构建第二个列表。有什么建议?谢谢!

;Splits a list into 2 if the word 'FOO' is present
;----------------------------------------------------------------------
;LOAD FILE: (load "C:\\split.lisp")
;USAGE: (split '(with great power foo comes great responsibility) '())
;OUTPUT: ((with great power)(comes great responsibility))

(defun split (x y)
  (cond
    ( ;IF: first element in list is nil
      (EQ (car x) nil)
        x ;RETURN the list
    )
    ( ;ELSE IF: first element is 'FOO'
      (EQ (car x) 'FOO)
        (cons (reverse y ) (cons (cdr x) nil)) 
    )
    ( ;ELSE: recursively call split but pass the rest of x and 
      ;prepend y with the head of x
      t
        (split (cdr x) (cons (car x) y))
    )
  ) ;END cond
) ;END split

4 个答案:

答案 0 :(得分:2)

第一次测试应该是不同的。

以下不是一个非常好的解决方案:它不是尾递归,它使用副作用。但还是......

(defun split (x)
  (cond ((null x) x)
        ((eq (first x) 'foo)
         (list nil (rest x)))
        (t (let ((l (split (rest x))))
             (push (first x) (first l))
             l))))

上面使用PUSH宏。 Common Lisp的一个有趣功能是您可以使用 places 进行修改。在这种情况下,我们修改要返回的列表的第一个子列表。我们将列表的第一个元素推送到第一个子列表。

CL-USER 12 > (split '(1 2 3 foo a b c))
((1 2 3) (A B C))

在Common Lisp中,通常会以非递归的方式编写解决方案。

在递归版本中,将函数简化为一个参数的典型方法是:使用一个参数编写函数,然后此函数调用带有两个参数的辅助函数。辅助函数也可以使用LABELS在本地定义。

答案 1 :(得分:2)

这是我对它的看法,只使用列表:

(defun split (lst)
  (labels ((split-rec (lst a)
    (cond
      ((or (null lst)
           (eq (car lst) 'foo))
             (values (reverse a) (cdr lst)))
      (t (split-rec (cdr lst) (cons (car lst) a))))))

    (split-rec lst ())))

split将大部分工作卸载到split-rec(在labels调用中定义),递归地使用令牌列表,直到它到达列表的末尾,或者看到“富。此时,它会立即获取列表的其余部分并将其视为第二个列表。因为第一个列表(a)是递归建立的,所以split-rec必须在返回之前将其反转。

这里有几个关于REPL的内容:

> (split '(with great power foo comes great responsibility))
(WITH GREAT POWER) ;
(COMES GREAT RESPONSIBILITY)

> (split '(with great power comes great responsibility))
(WITH GREAT POWER COMES GREAT RESPONSIBILITY) ;
NIL

> (split nil)
NIL ;
NIL

> (split '(with great power foo comes great foo responsibility) :on 'foo)
(COMES GREAT) ;
(WITH GREAT POWER RESPONSIBILITY)

> (split '(foo with great power comes great responsibility) :on 'foo)
NIL ;
(WITH GREAT POWER COMES GREAT RESPONSIBILITY)

处理了我能想到的大多数边缘情况,并且总是返回两个列表。呼叫者可以使用multiple-value-bind来获取两个列表,即:

(multiple-value-bind (a b)
  (split '(with great power foo comes great responsibility))
    ; do something useful with a and b
    )

答案 2 :(得分:1)

(defun split (lst)
  (let* ((a (make-array (length lst) :initial-contents lst))
         (index (position 'foo a)))
    (cond ((null index) a)
          (t (cons (loop for i from 0 to (1- index)
                      collect (aref a i))
               (list (loop for i from (1+ index) to (1- (length a))
                        collect (aref a i))))))))
  • 从列表中创建一个数组,以便更容易访问元素。
  • 检查foo是否存在,是否标记索引
  • 使用循环创建两个列表,foo之前的一个元素,以及foo之后的另一个元素,并将它们组合在一起。

答案 3 :(得分:0)

我在这里也试过了! :)

有一件事你想澄清一下:在像foo这样的角落里面是第一个要素,你应该返回两个列表还是只返回第二个?如果foo是列表中的最后一个元素,您应该返回列表和nil还是只返回第一个列表?如果列表中没有foo,您是否应仅返回列表或列表nil / nil并列出?

(defun split (list &key (on-symbol 'foo))
  (let (result result-head)
    (mapl
     #'(lambda (a)
         (if (eql (car a) on-symbol)
             (return-from split
               (if result
                   (values result (copy-list (cdr a)))
                   (copy-list (cdr a))))
             (if result
                 (setf (cdr result-head) (list (car a))
                       result-head (cdr result-head))
                 (setf result (list (car a))
                       result-head result)))) list) result))

(split '(1 2 3 4 5 foo a b c))
(split '(foo 1 2 3 4 5 foo a b c))
(split '(1 2 3 4 5 a b c))