Получить nth cdr списка без использования nthcdr
Я новичок в Common Lisp, борюсь с одним из моих заданий...
Одно из моих назначений CS - создать функцию, которая в основном работает как встроенная функция nthcdr в clisp.
Мы назовем это ncdr:
(ncdr n L) => n - это тот CDR, который мы хотим вывести, L список
Примеры:
(ncdr 2 '(a b c)) => (c)
(ncdr 0 '(a b c)) => (a b c)
(ncdr -1 '(a b c)) => nil
(ncdr 5 '(a b c)) => nil
Так что мой подход был установить счетчик и сравнить его с
Вот условия, о которых я подумал:
if n < 0 -> nil
if counter < n -> + 1 counter and (ncdr (cdr list)
if counter = n -> output list
if counter > n -> nil
И вот что я придумала... Я не думаю, что это самый эффективный способ, и на данный момент он не работает.
(defun ncdr (n L &aux counter)
(setq counter 0)
(cond
((atom L) nil)
((< n 0) nil)
((< counter n) (+ 1 counter (ncdr n (cdr L))))
((eq counter n) L)
((> counter n) nil) ))
Из тех часов, которые я провел, экспериментируя с каждой линией cond моей функции, я знаю, что эта строка не работает должным
((< counter n) (+ 1 counter (ncdr n (cdr L))))
Я получаю сообщение об ошибке: ноль не число
Я чувствую, что упускаю что-то критическое, чтобы решить это.
1 ответ
Во-первых, ошибка. В:
(+ 1 counter (ncdr n (cdr L))))
вы на самом деле суммируете три значения: 1
, значение переменной counter
и результат функции ncdr
применяется к двум параметрам, которые, когда nil
, выдает ошибку (вы суммируете, например, (+ 1 0 nil)
а также nil
это не число).
Тогда давайте перейдем к логике вашей программы.
Ваши условия в основном правильные, хотя они привели к программе, которая может быть улучшена, как мы увидим позже.
Но вы допустили логическую ошибку в их реализации: counter
должно меняться при каждом рекурсивном вызове функции, увеличиваясь на 1, тогда как для каждого вызова вы сбрасываете его в ноль в начале выполнения тела функции. Это потому, что приращение, даже если сделано правильно, отменяется присвоением (setq counter 0)
,
Как мы можем увеличить такую переменную правильно? Добавляя новый параметр counter
функционировать и избегать сброса его в ноль каждый раз, но только назначая его в ноль в начале, при первоначальном вызове.
Как это можно сделать в Common Lisp? Используя необязательный параметр, а не вспомогательную переменную, таким образом:
(defun ncdr (n L &optional (counter 0))
(cond ((atom L) nil)
((< n 0) nil)
((< counter n) (+ 1 counter (ncdr n (cdr L)))) ; <- Wrong!!! To be corrected
((eq counter n) L)
((> counter n) nil)))
Итак, давайте пересмотрим рекурсивную ветвь условного выражения, чтобы правильно передать новое значение в рекурсивном вызове (и выполнить некоторую незначительную корректировку):
(defun ncdr (n L &optional (counter 0))
(cond ((atom L) nil)
((< n 0) nil)
((< counter n) (ncdr n (cdr L) (+ 1 counter))) ; or (1+ counter)
((= counter n) L) ; use = for numbers
((> counter n) nil)))
&optional
Ключевое слово в лямбда-списке имеет эффект, который изначально вы называете (ncdr 2 '(a b c))
так counter
присваивается 0, в то время как в остальных вызовах текущее значение передается, увеличивается на 1, так что в конце рекурсии оно будет равно n
,
КПД
Нет необходимости использовать другую переменную, counter
, поскольку у вас уже есть переменная, подходящая для рекурсии: n
, На самом деле, вы могли бы переформулировать свои условия следующим образом:
if the list is empty, return nil
if n is less then 0, return nil
if n is 0, return the list
if n is greater than 0, recur on n-1 and on the cdr of the list
которые приводят к программе со следующей структурой:
(defun ncdr (n L)
(cond ((null L) nil)
((< n 0) nil)
((= n 0) L)
(t (ncdr ...)) ; <- recursive call - to be completed as exercise!
Для VisualLisp (AutoCAD):
(defun ncdr (n L)
(if (< n 0)
(setq L L)
(repeat (+ n 1) (setq L (cdr L)))
)
)
Это цикл, который отрезает первый элемент списка L n+1 раз. Начинается с 0.
пример:
(setq L (list 0 1 2 3 4)); -->(0 1 2 3 4)
(ncdr 0 L); --> (1 2 3 4)
(ncdr 1 L); --> (2 3 4)
(ncdr 2 L); --> (3 4)
(ncdr 3 L); --> (4)
(ncdr 4 L); --> nil