В чем разница между `(mcons (mcons '() 25) 16)` и `(mcons 25 (mcons 16 `()))`

Я занят структурой и интерпретацией Компьютерные программы, упражнение 2.18. Здесь мы должны определить процедуру reverse, чтобы перевернуть список. Он должен сделать следующее:

(reverse (list 1 4 9 16 25))
;; => (25 16 9 4 1)

Я придумал следующее определение:

(define (reverse list)
  (if (null? list) 
      list
      (cons (reverse (cdr list)) (car list))))
;; => (mcons (mcons (mcons (mcons (mcons '() 25) 16) 9) 4) 1).

Затем в решении нашел что-то похожее на следующее:

(define (reverse items) 
  (if (null? (cdr items)) 
      items 
      (append (reverse (cdr items)) 
              (cons (car items) nil))))
;; => (mcons 25 (mcons 16 (mcons 9 (mcons 4 (mcons 1 '()))))).

Между append и cons есть разница, которую я не могу указать.

Мой вопрос: в чем разница и почему результаты не отображаются как (25 16 9 4 1)?


person Erwin Rooijakkers    schedule 17.01.2015    source источник


Ответы (1)


Краткий ответ: первая версия reverse неправильно строит неправильный список, вторая версия неэффективно строит правильный список. Мы можем работать лучше, если понимаем разницу между append и cons.

append объединяет два списка. Если мы будем использовать его для простого добавления элемента в конец одного списка, мы будем делать гораздо больше работы, чем нужно: нам придется проходить весь список каждый раз, чтобы поместить последний элемент (см.: алгоритм Шлемиэля-художника). Следовательно, реализация reverse, использующая append, может быть настолько же плохой, как O(n^2) по сложности.

С другой стороны, cons добавляет один элемент в начало списка для O(n) сложности реализации reverse. В общем, в Scheme вы должны стараться избегать использования append для создания нового выходного списка, всегда предпочитая cons. Теперь давайте посмотрим, что возвращает ваш алгоритм с использованием cons:

(reverse '(1 2 3 4 5))
=> '(((((() . 5) . 4) . 3) . 2) . 1)

Почему это? потому что для создания правильного списка с cons вторым аргументом должен быть другой правильный список, но вы передаете один элемент. Для корректной реализации требуется параметр-аккумулятор — и, кстати, он более эффективен, поскольку использует хвостовую рекурсию — концепцию, с которой вы уже должны быть знакомы, поскольку она была представлена ​​в разделе 1.2 книги. Попробуй это:

(define (reverse lst acc)
  (if (null? lst)
      acc
      (reverse (cdr lst) (cons (car lst) acc))))

(reverse '(1 2 3 4 5) '())
=> '(5 4 3 2 1)

Что касается последней части вопроса: список отображается с mcons (m означает, что cons ячеек являются изменяемыми) из-за используемого вами языка, попробуйте переключиться на #lang racket, который использует < em>неизменяемые cons ячейки по умолчанию и будут напечатаны так, как вы ожидаете.

person Óscar López    schedule 17.01.2015
comment
Большое спасибо! Я использовал #lang planet neil/sicp, но вернусь к #lang racket. - person Erwin Rooijakkers; 18.01.2015
comment
@user2609980 user2609980 не переключайтесь так быстро, neil/sicp подходит для выполнения упражнений SICP, тогда как у Racket будут некоторые особенности. Было бы лучше, если бы вы просто привыкли к тому, что списки печатаются со всеми mcons показами. - person Óscar López; 18.01.2015