【问题标题】:Does Lisp have something like Haskell's takeWhile function?Lisp 有类似 Haskell 的 takeWhile 函数吗?
【发布时间】:2011-05-29 13:51:04
【问题描述】:

我是 Common Lisp 的新手。在 Haskell 中,您可以执行以下操作:

Prelude> takeWhile (<= 10) [k | k <- [1..]]
[1,2,3,4,5,6,7,8,9,10]

这在 Lisp 中可行吗?不一定是无限列表,而是任何列表。

【问题讨论】:

    标签: haskell functional-programming lisp common-lisp


    【解决方案1】:

    你可以使用LOOP:

    (setq *l1* (loop for x from 1 to 100 collect x))
    (loop for x in *l1* while (<= x 10) collect x)
    

    如果你真的需要它作为一个单独的函数:

    (defun take-while (pred list)
      (loop for x in list
            while (funcall pred x)
            collect x))
    

    我们在这里:

    T1> (take-while (lambda (x) (<= x 10)) *l1*)
    (1 2 3 4 5 6 7 8 9 10)
    

    但如果我们比较:

    (loop for x in *l1* while (<= x 10) collect x)
    (take-while (lambda (x) (<= x 10)) *l1*)
    

    我想我会坚持使用循环。

    对于无限序列,您可以查看Series

    T1> (setq *print-length* 20)
    20
    T1> (setq *l1* (scan-range :from 1))
    #Z(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...)
    T1> (until-if (lambda (x) (> x 10)) *l1*)
    #Z(1 2 3 4 5 6 7 8 9 10)
    

    【讨论】:

      【解决方案2】:

      这应该可以...

      (defun take-while (list test)
        (and list (funcall test (car list))
             (cons (car list) (take-while (cdr list) test))))
      
      (take-while '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) (lambda (x) (< x 10)))
      --> (1 2 3 4 5 6 7 8 9)
      

      然而,这种“自然”的实现不是尾递归的,并且对于大列表可能会崩溃。

      一个明确的 push-nreverse 方法(一种常见的模式)可以是

      (defun take-while (list test)
        (do ((res nil))
            ((or (null list) (not (funcall test (car list))))
               (nreverse res))
          (push (car list) res)
          (setf list (cdr list))))
      

      递归(但尾递归,因此可能适用于大多数 CL 实现)可以 IMO 如下:

      (defun take-while (list test)
        (labels ((rec (res x)
                   (if (and x (funcall test (car x)))
                       (rec (cons (car x) res) (cdr x))
                       (nreverse res))))
          (rec nil list)))
      

      请注意,但不能保证通用 lisp 实现会处理尾调用优化。

      【讨论】:

      • 不是一个好主意。它甚至不是尾递归的。它会炸毁任何更长列表上的堆栈...
      • 感谢您的接受,但请注意,可能 danlei 的答案更好......这甚至不是尾递归
      • @rainer joswig:我同意循环更好
      • 重新接受。它解决了我眼前的问题,但我明白你的意思。
      【解决方案3】:

      CL-LAZY library 实现了对 Common Lisp 的惰性调用,并提供了一个可感知惰性的 take-while 函数。您可以使用Quicklisp 安装并试用。

      【讨论】:

        【解决方案4】:

        某些语言提供 Haskell 样式的列表 API 作为 3rd 方库,支持或不支持无限流。

        一些例子:

        请记住,takeWhile 在序列上相对容易实现,在 Haskell 中给出如下:

        takeWhile _ []          =  []
        takeWhile p (x:xs)
                    | p x       =  x : takeWhile p xs
                    | otherwise =  []
        

        【讨论】:

        • 虽然它没有确定我的直接问题,但这个答案非常有见地。谢谢!
        【解决方案5】:

        您可以使用闭包在 common lisp 中进行惰性求值(来自 Paul Graham's On Lisp):

        (defun lazy-right-fold (comb &optional base)
          "Lazy right fold on lists."
          (labels ((rec (lst)
                     (if (null lst)
                         base
                         (funcall comb
                                  (car lst)
                                  #'(lambda () (rec (cdr lst)))))))
            #'rec))
        

        那么,take-while就变成了:

        (defun take-while (pred lst)
          (lazy-right-fold #'(lambda (x f) (
                               (if (test x)
                                   (cons x (funcall f))
                                   (funcall f)))
                           nil))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-10-23
          • 1970-01-01
          • 2011-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-23
          相关资源
          最近更新 更多