0

I'm a Common Lisp beginner struggling with one of my assignment...

One of my CS assignment is to create a function which basically works as the built-in nthcdr function of clisp.

We will call it ncdr:

(ncdr n L) => n being the cdr we want to output, L the list

Examples:

(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

So my approach was to set a counter and compare it to n

Here are the conditions I thought of:

if n < 0 -> nil 
if counter < n -> + 1 counter and (ncdr (cdr list)
if counter = n -> output list
if counter > n -> nil 

And here is what I came up with... I don't think it's the most effective way though and, for now, well it doesn't work.

(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) ))

From the hours I spent experimenting with each cond line of my function I know this line doesn't work as intended

((< counter n) (+ 1 counter (ncdr n (cdr L))))

I get an error: nil is not an number

I feel like I'm missing something critical to solve this.

Yoshiki
  • 57
  • 5
  • your conditions are... well not ok. what should happen when `ncdr` returns `nil`? and `nil` is what you return in 3 out of 5 cases. hmm... did you thought about something like`(cond ((null L) nil) ((< n 0) nil) ((zerop n) L) (t (ncdr (- n 1) (cdr L))))`? – rsm Aug 13 '17 at 01:37
  • also: don't use EQ to compare numbers. Use EQL or =. – Rainer Joswig Aug 13 '17 at 05:41
  • why would you need N and a COUNTER? – Rainer Joswig Aug 13 '17 at 05:42
  • 1
    I see how I made things extra difficult now, thank you for helping me out ! – Yoshiki Aug 13 '17 at 09:15
  • @RainerJoswig EQL doesn't work on numbers. when types are different, EQL returns NIL. `(eql 1.0 1) => NIL` but `(equalp 1.0 1) => T` and `(= 1.0 1) => T` – rsm Aug 13 '17 at 21:56
  • @rsm 'when types are different, `EQL` returns `NIL`', that's true. `=` converts the integer to a float of the same format. Still the result can be `NIL`: `(equalp 123456789.0 123456789)` -> `NIL` and `(= 123456789.0 123456789)` is also `NIL`. That `=` does convert the integer to a float, does not mean it is a float with an equal value. – Rainer Joswig Aug 13 '17 at 23:44

2 Answers2

5

First, the error. In:

(+ 1 counter (ncdr n (cdr L))))

you are actually summing three values: 1, the value of the variable counter, and the result of the function ncdr applied to two parameters, which, when nil, produces the error (you are summing, for instance, (+ 1 0 nil) and nil is not a number).

Then, let’s go to the logic of your program.

Your conditions are basically correct, although they led to a program that could be improved, as we will see later.

But, you made a logical error in their implementation: counter should change at each recursive call of the function, increasing by 1, while for each call, you reset it to zero at the beginning of the execution of the body of the function. This is because the increment, even if done correctly, is canceled by the assignment (setq counter 0).

How can we increment such a variable correctly? By adding a new parameter counter to function, and avoiding to reset it to zero each time, but only assigning it to zero at the beginning, in the initial call.

How could this be done in Common Lisp? Using an optional parameter, not a auxiliary variable, in this way:

(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)))

So, let’s revise the recursive branch of the conditional, to correctly pass the new value in the recursive call (and perform some minor adjustment):

(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)))

The &optional keyword in the lambda list has the effect that initially you call (ncdr 2 '(a b c)) so counter is assigned to 0, while in the remaining calls the current value is passed along, incremented by 1, so that at the end of recursion it will be equal to n.

Efficiency

There is no need to use another variable, counter, since you already have a variable suitable for the recursion: n. In fact, you could reformulate your conditions in this way:

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

that bring to a program with the following structure:

(defun ncdr (n L)
  (cond ((null L) nil)
        ((< n 0) nil)
        ((= n 0) L)
        (t (ncdr ...))  ; <- recursive call - to be completed as exercise!
Renzo
  • 26,848
  • 5
  • 49
  • 61
  • Thanks a lot for your answer, I now understand my mistakes much better ! I didn't think of using n for the recursive call, it works perfectly and is much more elegant. Also I wasn't very familiar with the difference between &aux and &optional, I see how optional is more adapted here. Thanks a million! – Yoshiki Aug 13 '17 at 09:37
0

for VisualLisp (AutoCAD):

   (defun ncdr (n L)
     (if (< n 0)
       (setq L L)
       (repeat (+ n 1) (setq L (cdr L)))
       )
     )

It's a loop that cuts of the first element of a list L for n+1 times. Starts from 0.

example:

(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