2009-07-09 3 views
4

Я хочу написать функцию, которая удаляет конечный нуль из списка. Я впервые попробовал написать его элегантно с помощью рекурсии, но в итоге так:Lisp: Элегантный способ вырезать конечный ноль из списка? (Обзор)

(defun strip-tail (lst) 
    (let ((last-item-pos (position-if-not #'null lst :from-end t))) 
    (if last-item-pos 
     (subseq lst 0 (1+ last-item-pos))))) 

; Test cases. 
(assert (eq nil (strip-tail nil))) 
(assert (eq nil (strip-tail '(nil)))) 
(assert (equal '(a b) (strip-tail '(a b nil nil)))) 
(assert (equal '(a nil b) (strip-tail '(a nil b nil)))) 
(assert (equal '(a b) (strip-tail '(a b)))) 

Это, возможно, ясно, но я не уверен. Есть ли более ленивый способ сделать это?

+0

Разве он не должен быть lst вместо нуля в самом конце? Я предполагаю (strip-tail '(a b c d)) должен вернуться (a b c d). – sigjuice

+0

Нет, это должно быть ноль. Да, так оно и должно работать. –

+0

Doh! – sigjuice

ответ

1

Вот что я придумал, если вы не против этого быть разрушительным:

(defvar foo (list 'a 'b 'c nil 'd 'e 'nil 'nil 'f nil nil)) 

(defun get-last-non-nil (list &optional last-seen) 
    (if list 
     (if (car list) 
      (get-last-non-nil (cdr list) list) 
      (get-last-non-nil (cdr list) last-seen)) 
     last-seen)) 

(defun strip-tail (list) 
    (let ((end (get-last-non-nil list))) 
    (if (consp end) 
     (when (car end) (setf (cdr end) nil) list)))) 

(strip-tail foo) -> (A B C NIL D E NIL NIL F) 
+0

Не удается утверждать # 1 и # 2. –

+0

@ kotlinski Хорошо, теперь, когда я пытаюсь проверить утверждения, кажется, что они проходят, хотя я бы не назвал его «элегантным». Второе утверждение - случай, когда ему нельзя доверять, чтобы делать свою вещь на месте, так что, как и nreverse, вы должны использовать возвращаемое значение. – kwatford

+0

+1, потому что это возможно наиболее эффективное решение. Хотя, не функциональный, он все еще «ленив», демонстрируя гибкость. – firefrorefiddle

3

Ну, версия будет:

  1. обратного список по
  2. удалить ведущую Nils
  3. свернуть переписку

Код:

(defun list-right-trim (list &optional item) 
    (setf list (reverse list)) 
    (loop for e in list while (eq item e) do (pop list)) 
    (reverse list)) 

Вот еще один вариант:

  1. итерация по списку и отметьте положение первого нуля, который только затем Нильс
  2. возвратных суб-последовательность

код:

(defun list-right-trim (list &aux (p nil)) 
    (loop for i from 0 and e in list 
    when (and (null p) (null e)) 
    do (setf p i) 
    else when (and p e) do (setf p nil)) 
    (if p (subseq list 0 p) list)) 
+0

Любопытно, почему параметр aux вместо явного позволяет определить p? В противном случае я бы сделал в основном то же самое (с долистом вместо цикла, но это только стиль): (defun list-right-trim (list) (let ((last-pos 0) (i 0)) (dolist (список предметов) (incf i) (когда item (setf last-pos i))) (нижний предел 0 последний-пункт))) – khedron

+0

Нет особых причин. & aux - это как LET - с уровнем отступов меньше. –

0

Я попытался с помощью рекурсии, но он не работает на GNU CL:

(defun strip(lst) 
    (if (null (last lst)) 
     (strip (butlast lst))    
    lst)) 

идея:

  1. тест, если элемент последний список равен нулю, если так сделать рекурсивный вызов с последний элемент удаляется (butlast)
  2. затем возвращают сам список
+2

LAST возвращает последние минусы, а не последний элемент. Эта функция становится неэффективной, если в списке много нулей в конце, так как для каждого удаленного NIL мы должны дважды пересекать список. –

+0

Почему эта функция будет пересекать список дважды? – dfa

+2

LAST пересекает его один раз и снова BUTLAST. –

-1

Ну, это я на самом деле это не ответ, но я подумал, что я тоже поставил бы это здесь, чтобы он был лучше видимым.

В вашей первоначальной реализации, как вы думаете, следует обрабатывать элементы без списка?

* (strip-tail "abcde") 

"abcde" 
* (strip-tail 42) 

debugger invoked on a TYPE-ERROR in thread #<THREAD "initial thread" {A69E781}>: 
    The value 42 is not of type SEQUENCE. 
+0

Мне нужно только обрабатывать списки ... –

3
(defun strip-tail (ls) 
    (labels ((strip-car (l) 
        (cond ((null l)  nil) 
         ((null (car l)) (strip-car (cdr l))) 
         (t    l)))) 
     (reverse (strip-car (reverse ls))))) 

Пример запуска (против ваших тестов):

[1]> (assert (eq nil (strip-tail nil))) 
NIL 
[2]> (assert (eq nil (strip-tail '(nil)))) ;' 
NIL 
[3]> (assert (equal '(a b) (strip-tail '(a b nil nil)))) 
NIL 
[4]> (assert (equal '(a nil b) (strip-tail '(a nil b nil)))) 
NIL 
[5]> (assert (equal '(a b) (strip-tail '(a b)))) 
NIL 
[6]> 
+0

Является ли это более эффективным, чем исходное предложение? Мне кажется, что ему нужно перевернуть список дважды, а не один раз. – firefrorefiddle

2

Как насчет этого?

(defun strip-tail (lst) 
    (if lst 
    (let ((lst (cons (car lst) (strip-tail (cdr lst))))) 
     (if (not (equal '(nil) lst)) lst)))) 

... интересно, как сделать хвост-рекурсивным, хотя эта версия исчерпала бы стек для больших списков.

Смежные вопросы