У Lisp есть что-то вроде функции takeWhile Хаскелла?

Я новичок в Common Lisp. В Haskell вы можете сделать что-то вроде этого:

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

возможно ли это в Lisp? Не обязательно с бесконечным списком, но с любым списком.

5 ответов


вы могли бы использовать цикл:

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

Я думаю, что я бы просто придерживаться цикла.

для бесконечных последовательностей вы можете взглянуть на серия:

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)

Это следует делать...

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

рекурсивный (но хвост-рекурсивный, поэтому наверное ok с большинством реализаций 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 будет обрабатывать оптимизация хвостового вызова.


некоторые языки предоставляют API списка в стиле Haskell в качестве сторонних библиотек с поддержкой или без поддержки бесконечных потоков.

примеры:

помните, что takeWhile относительно легко реализовать над последовательностью и дается в Haskell как:

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

на CL-ленивая библиотека реализует ленивый вызов Common Lisp и предоставляет функцию take-while, которая осознает лень. Вы можете установить его с помощью Quicklisp и попробуйте.


вы можете иметь ленивую оценку в 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))

затем, take-while становится:

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