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:
我想编写一个从列表中删除尾随nil的函数。我首先尝试用递归优雅地编写它,但结果是这样的:
(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?
这可以说是明确的,但我不相信。是否有更多的lispy方式来做到这一点?
6 个解决方案
#1
Well, a version would be:
那么,一个版本将是:
- reverse the list
- remove leading nils
- reverse the list
反转清单
删除领先的nils
反转清单
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:
这是另一种变体:
- iterate over the list and note the position of the first nil which is only followed by nils
- return the sub-sequence
迭代列表并记下第一个nil的位置,后面只有nils
返回子序列
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))
#2
(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]>
#3
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.
...想知道如何使其尾递归,这个版本会耗尽大型列表的堆栈。
#4
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)
#5
I tried using recursion but it doesn't work on GNU CL:
我尝试使用递归,但它不适用于GNU CL:
(defun strip(lst)
(if (null (last lst))
(strip (butlast lst))
lst))
the idea is:
这个想法是:
- test if the last list element is nil, if so make a recursive call with the last element removed (butlast)
- then return the list itself
测试最后一个列表元素是否为nil,如果是,则删除最后一个元素进行递归调用(butlast)
然后返回列表本身
#6
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.
#1
Well, a version would be:
那么,一个版本将是:
- reverse the list
- remove leading nils
- reverse the list
反转清单
删除领先的nils
反转清单
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:
这是另一种变体:
- iterate over the list and note the position of the first nil which is only followed by nils
- return the sub-sequence
迭代列表并记下第一个nil的位置,后面只有nils
返回子序列
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))
#2
(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]>
#3
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.
...想知道如何使其尾递归,这个版本会耗尽大型列表的堆栈。
#4
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)
#5
I tried using recursion but it doesn't work on GNU CL:
我尝试使用递归,但它不适用于GNU CL:
(defun strip(lst)
(if (null (last lst))
(strip (butlast lst))
lst))
the idea is:
这个想法是:
- test if the last list element is nil, if so make a recursive call with the last element removed (butlast)
- then return the list itself
测试最后一个列表元素是否为nil,如果是,则删除最后一个元素进行递归调用(butlast)
然后返回列表本身
#6
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.