2011-05-29 50 views
6

我是Common Lisp的新手。在Haskell中,你可以做一些這樣的事情:Lisp有Haskell的takeWhile函數嗎?

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

這在Lisp中可能嗎?不一定與無限列表,但與任何列表。

回答

11

你可以使用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) 
4

這應該做...

(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) 

但是這種「自然」的實施是不是尾遞歸和可能崩潰對於大名單。

的顯式推入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實現將處理尾呼優化。

+1

不是一個好主意。它甚至不是尾遞歸。它會吹出更多列表上的堆棧... – 2011-05-29 14:41:33

+1

感謝您接受,但請注意,可能danlei答案更好...這甚至不是尾遞歸 – 6502 2011-05-29 14:41:34

+1

@rainer joswig:我同意循環更好 – 6502 2011-05-29 14:42:38

3

某些語言提供了一個Haskell風格的列表API作爲第三方庫,支持或不支持無限流。

一些例子:

記住takeWhile是比較容易實現的一個序列,並在哈斯克爾給出如下:

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

雖然它並沒有確定我的直接問題,但這個答案非常有見地。謝謝! – Mike 2011-05-29 14:15:14

3

CL-LAZY library實現對Common Lisp的惰性調用,並提供懶惰感知的take-while函數。您可以使用Quicklisp進行安裝並試用。

+0

嘿,這很酷!謝謝! – Mike 2011-05-29 16:39:56

0

你可以有(從Paul Graham's On Lisp)使用閉包在Common 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)) 

然後,取而變爲:

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