是否有一个常见的lisp宏用于从列表中弹出第n个元素?

时间:2010-11-04 04:13:56

标签: macros lisp common-lisp

我对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))))

4 个答案:

答案 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宏可以使用,也适用于setfrotatef等所有其他宏

;; 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的小值,代码可以基于“尸体”发出特殊变体:cddrcdddr,而不是普通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有同样的怀疑...如果我没记错...... pushpop都不能被定义为修改宏,前者因为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)