views:

562

answers:

6

I want to write a function that removes trailing nil's from a list. I first tried to write it elegantly with recursion, but ended up like this:

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

It's arguably clear, but I'm not convinced. Is there a more lispy way to do it?

A: 

Here's what I came up with, assuming you don't mind this being destructive:

(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)
kwatford
It fails assert #1 and #2.
kotlinski
@kotlinski Ok, now that I bother to check the asserts it seems to pass, though I wouldn't call it 'elegant'. The second assert is a case where it can't be trusted to do its thing in-place, so like nreverse you've got to use the return value.
kwatford
+1  A: 

Well, a version would be:

  1. reverse the list
  2. remove leading nils
  3. reverse the list

The code:

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

Here is another variant:

  1. iterate over the list and note the position of the first nil which is only followed by nils
  2. return the sub-sequence

the code:

(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))
Rainer Joswig
khedron
Rainer Joswig
A: 

I tried using recursion but it doesn't work on GNU CL:

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

the idea is:

  1. test if the last list element is nil, if so make a recursive call with the last element removed (butlast)
  2. then return the list itself
dfa
LAST returns the last cons and not the last element.This function gets inefficient if the list has lots of nils at the end, since for every removed NIL we have to traverse the list twice.
Rainer Joswig
why this function will traverse the list twice?
dfa
LAST traverses it once and BUTLAST again.
Rainer Joswig
A: 

Well, this is not really an answer, but I thought I'd put this here as well so it has better visibility.

In your original implementation, do you think non-list items should be handled?

* (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.
sigjuice
I only need it to handle lists...
kotlinski
+2  A: 
(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)))))

Sample run (against your test cases):

[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]>
dsm
+2  A: 

How about this?

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

...wonder how to make it tail-recursive though, this version would exhaust the stack for large lists.

kotlinski