it-roy-ru.com

реверсивный список в Лиспе

Я пытаюсь перевернуть список в LISP, но получаю ошибку: "Ошибка: Исключение C0000005 [flags 0] в 20303FF3 {Смещение 25 внутри #} eax 108 ebx 200925CA ecx 200 edx 2EFDD4D esp 2EFDCC8 ebp 2EFDCE0 esi 628 edi 628 "

Мой код выглядит следующим образом:

(defun rev (l)
    (cond
        ((null l) '())
        (T (append (rev (cdr l)) (list (car l)))))) 

Может кто-нибудь сказать мне, что я делаю не так? Заранее спасибо!

5
Nelly

Ваш код в том виде, в котором он написан, является логически правильным и дает результат, который вы хотели бы получить

CL-USER> (defun rev (l)
           (cond
             ((null l) '())
             (T (append (rev (cdr l)) (list (car l)))))) 
REV
CL-USER> (rev '(1 2 3 4))
(4 3 2 1)
CL-USER> (rev '())
NIL
CL-USER> (rev '(1 2))
(2 1)

Тем не менее, есть некоторые проблемы с ним с точки зрения производительности. Функция append создает копию всего, кроме последнего аргумента. Например, когда вы делаете (добавьте '(1 2)' (ab) '(3 4)), вы создаете четыре новые cons-ячейки, машины которых 1, 2, a и б. CDR последнего является существующим списком (3 4). Это потому, что реализация append выглядит примерно так:

(defun append (l1 l2)
  (if (null l1)
      l2
      (cons (first l1)
            (append (rest l1)
                    l2))))

Это не совсем Common LISP добавление, потому что Common LISP добавление может принимать более двух аргументов. Это достаточно близко, чтобы продемонстрировать, почему копируется все, кроме последнего списка. Теперь посмотрим, что это означает с точки зрения вашей реализации rev, хотя:

(defun rev (l)
  (cond
    ((null l) '())
    (T (append (rev (cdr l)) (list (car l)))))) 

Это означает, что когда вы переворачиваете список наподобие (1 2 3 4), это выглядит так:

(append '(4 3 2) '(1))              ; as a result of    (1)
(append (append '(4 3) '(2)) '(1))  ; and so on...      (2)

Теперь в строке (2) вы копируете список (4 3). В первой строке вы копируете список (4 3 2), который включает в себя копию (4 3). То есть вы копируете копию . Это довольно расточительное использование памяти.

Более распространенный подход использует переменную аккумулятора и вспомогательную функцию. (Обратите внимание, что я использую endp, rest, first и list * вместо null, cdr, car и cons, поскольку становится понятнее, что мы работаем со списками, а не с произвольными cons-деревьями. Они почти одинаковые (но есть несколько отличий).

(defun rev-helper (list reversed)
  "A helper function for reversing a list.  Returns a new list
containing the elements of LIST in reverse order, followed by the
elements in REVERSED.  (So, when REVERSED is the empty list, returns
exactly a reversed copy of LIST.)"
  (if (endp list)
      reversed
      (rev-helper (rest list)
                  (list* (first list)
                         reversed))))
CL-USER> (rev-helper '(1 2 3) '(4 5))
(3 2 1 4 5)
CL-USER> (rev-helper '(1 2 3) '())
(3 2 1)

С помощью этой вспомогательной функции легко определить rev:

(defun rev (list)
  "Returns a new list containing the elements of LIST in reverse
order."
  (rev-helper list '()))
CL-USER> (rev '(1 2 3))
(3 2 1)

Тем не менее, вместо использования внешней вспомогательной функции, было бы более распространенным использовать метки для определения локальной вспомогательной функции:

(defun rev (list)
  (labels ((rev-helper (list reversed)
             #| ... |#))
    (rev-helper list '())))

Или, поскольку Common LISP не гарантирует оптимизацию оконечных вызовов, цикл do также хорош и чист:

(defun rev (list)
  (do ((list list (rest list))
       (reversed '() (list* (first list) reversed)))
      ((endp list) reversed)))
4
Joshua Taylor

В ANSI Common LISP вы можете перевернуть список с помощью функции reverse (неразрушающий: выделяет новый список) или nreverse (реорганизует строительные блоки или данные из существующего списка для создания обращенного).

> (reverse '(1 2 3))
(3 2 1)

Не используйте nreverse для литералов в цитируемых списках; это неопределенное поведение и может вести себя удивительным образом, поскольку это де-факто самоизменяющийся код.

2
Kaz

Если вы можете использовать стандартные функции библиотеки CL, такие как append , вы должны использовать reverse (как Kaz предложил ).

В противном случае, если это упражнение (ч/б или нет), вы можете попробовать это:

(defun rev (l)
  (labels ((r (todo)
             (if todo
                 (multiple-value-bind (res-head res-tail) (r (cdr todo))
                   (if res-head
                       (setf (cdr res-tail) (list (car todo))
                             res-tail (cdr res-tail))
                       (setq res-head (list (car todo))
                             res-tail res-head))
                   (values res-head res-tail))
                 (values nil nil))))
    (values (r l))))

PS. Ваша конкретная ошибка непонятна, пожалуйста, свяжитесь с вашим поставщиком.

0
sds

Вы, вероятно, исчерпали место в стеке; это является следствием вызова рекурсивной функции rev вне хвостовой позиции. Подход к преобразованию в хвостовую рекурсивную функцию заключается во введении аккумулятора, переменной result, в следующем:

(defun reving (list result)
  (cond ((consp list) (reving (cdr list) (cons (car list) result)))
        ((null list) result)
        (t (cons list result))))

Ваша функция rev становится:

(define rev (list) (reving list '()))

Примеры:

* (reving '(1 2 3) '())
(3 2 1)
* (reving '(1 2 . 3) '())
(3 2 1)

* (reving '1 '())
(1)
0
GoZoner