Есть ли общий макрос для отображения списка элементов n-го списка? - PullRequest
5 голосов
/ 04 ноября 2010

Я довольно новичок в сцене 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 ]

10 голосов
/ 04 ноября 2010

Примерно так:

Удаление n-го элемента списка :

(defun remove-nth (list n)
  (remove-if (constantly t) list :start n :end (1+ n)))

постоянно возвращает функцию, которая всегда возвращает свой аргумент.

В качестве макроса, который принимает место , используя 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)
5 голосов
/ 16 июля 2013

Да, в Лиспе есть макрос для выталкивания 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 и, таким образом, являются избыточными.

1 голос
/ 04 ноября 2010

Я нашел решение, которое немного более эффективно, чем моя первая попытка:

(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)
0 голосов
/ 20 июня 2012

У меня такое же подозрение, как у @ 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)
...