我对Common Lisp场景很新鲜,我似乎无法找到一个快速的方法从列表中获取第n个元素并同时从列表中删除它。我已经做到了,但它并不漂亮,我真正喜欢的是像“pop”这样的东西,但却采用了第二个参数:
(setf x '(a b c d))
(setf y (popnth 2 x))
; x is '(a b d)
; y is 'c
我很确定“popnth”必须是一个宏,以防参数为0并且它必须表现得像“pop”。
编辑:这是我的垃圾第一版:(defmacro popnth (n lst)
(let ((tempvar (gensym)))
`(if (eql ,n 0)
(pop ,lst)
(let ((,tempvar (nth ,n ,lst)))
(setf (cdr (nthcdr ,(- n 1) ,lst)) (nthcdr ,(+ n 1) ,lst))
,tempvar))))
答案 0 :(得分:10)
这样的事情:
删除列表的第n个元素:
(defun remove-nth (list n)
(remove-if (constantly t) list :start n :end (1+ n)))
constantly返回一个总是返回其参数的函数。
作为接受place的宏,使用define-modify-macro :
(define-modify-macro remove-nth-f (n) remove-nth "Remove the nth element")
<强> POP-NTH 强>
(defmacro pop-nth (list n)
(let ((n-var (gensym)))
`(let ((,n-var ,n))
(prog1 (nth ,n-var ,list)
(remove-nth-f ,list ,n-var)))))
示例强>:
CL-USER 26 > (defparameter *list* (list 1 2 3 4))
*LIST*
CL-USER 27 > (pop-nth *list* 0)
1
CL-USER 28 > *list*
(2 3 4)
CL-USER 29 > (pop-nth *list* 2)
4
CL-USER 30 > *list*
(2 3)
答案 1 :(得分:5)
是的,Lisp有一个用于弹出列表第N个元素的宏:它被称为pop
。
$ clisp -q
[1]> (defvar list (list 0 1 2 3 4 5))
LIST
[2]> (pop (cdddr list))
3
[3]> list
(0 1 2 4 5)
[4]>
pop
适用于任何表示地点的形式。
问题是,与cddr
不同,nthcdr
不是访问者;像(nthcdr 3 list)
这样的表格并不表示某个地方;它只能作为函数调用。
写一个pop
的专门形式并不是最好的答案;相反,我们可以通过编写nthcdr
的克隆来实现更一般的修复,其行为类似于地点访问器。然后pop
宏可以使用,也适用于setf
和rotatef
等所有其他宏。
;; our clone of nthcdr called cdnth
(defun cdnth (idx list)
(nthcdr idx list))
;; support for (cdnth <idx> <list>) as an assignable place
(define-setf-expander cdnth (idx list &environment env)
(multiple-value-bind (dummies vals newval setter getter)
(get-setf-expansion list env)
(let ((store (gensym))
(idx-temp (gensym)))
(values dummies
vals
`(,store)
`(let ((,idx-temp ,idx))
(progn
(if (zerop ,idx-temp)
(progn (setf ,getter ,store))
(progn (rplacd (nthcdr (1- ,idx-temp) ,getter) ,store)))
,store))
`(nthcdr ,idx ,getter)))))
测试:
$ clisp -q -i cdnth.lisp
;; Loading file cdnth.lisp ...
;; Loaded file cdnth.lisp
[1]> (defvar list (list 0 1 2 3 4 5))
LIST
[2]> (pop (cdnth 2 list))
2
[3]> list
(0 1 3 4 5)
[4]> (pop (cdnth 0 list))
0
[5]> list
(1 3 4 5)
[6]> (pop (cdnth 3 list))
5
[7]> list
(1 3 4)
[8]> (pop (cdnth 1 list))
3
[9]> list
(1 4)
[10]> (pop (cdnth 1 list))
4
[11]> list
(1)
[12]> (pop (cdnth 0 list))
1
[13]> list
NIL
[14]>
实现的一个可能的改进是分析idx
表单并优化生成的代码,该代码实现对idx
的值的运行时检查。也就是说,如果idx
是常量表达式,则不需要发出测试idx
是否为零的代码。可以发出适当的代码变体。不仅如此,对于idx
的小值,代码可以基于“尸体”发出特殊变体:cddr
,cdddr
,而不是普通nthcdr
。但是,其中一些优化可能由Lisp编译器完成,因此是多余的。
答案 2 :(得分:1)
我想出了一个比我第一次尝试更有效的解决方案:
(defmacro popnth (n lst)
(let ((t1 (gensym))(t2 (gensym)))
`(if (eql ,n 0)
(pop ,lst)
(let* ((,t1 (nthcdr (- ,n 1) ,lst))
(,t2 (car (cdr ,t1))))
(setf (cdr ,t1) (cddr ,t1))
,t2))))
这是实际行动:
[2]> (defparameter *list* '(a b c d e f g))
*LIST*
[3]> (popnth 3 *list*)
D
[4]> *list*
(A B C E F G)
[5]> (popnth 0 *list*)
A
[6]> *list*
(B C E F G)
答案 3 :(得分:0)
我和@ 6502有同样的怀疑...如果我没记错...... push
和pop
都不能被定义为修改宏,前者因为place
是不是它的第一个参数,后者是因为它的返回值不是修改后的对象。
define-modify-macro
表单(define-modify-macro m (p1 ... pn) f)
的表达式定义了一个新的宏m
,因此调用(m place a1 ... an)
形式会导致place
设置为(f val a1 ... an)
},其中val
表示place
的值。参数还可以包括休息和可选参数。字符串(如果存在)将成为新宏的文档。
我有popnth
可以正常工作:
(defun nthpop (index lst)
(pop (nthcdr (1- index) lst)))
> *list*
(1 2 3 4 5)
> (nthpop 2 *list*)
2
> *list*
(1 3 4 5)