Получить 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
Другие вопросы по тегам