Home > Enterprise >  Common Lisp function that shortens a list
Common Lisp function that shortens a list

Time:11-14

I am a super CL beginner and I am very stumped on the following task:

Define a recursive function shorten that deletes the last n elements from a given list. The function should return the shortened list at the end.

For example:

 (shorten 5 '(1 2 3 4 5 6 7 8 9 10))
 =>
 (1 2 3 4 5)

Maybe there ist someone who could help me.

Thanks a lot!

CodePudding user response:

My plan to do this would be, having p0 pointing at the list's start, go along the list n times and get p2; then use p1 initially equal to p0 and go along the list each time incrementing both p1 and p2 until p2 points at the list's end; then (rplacd p1 nil); and finally return p0.

Adjust the above for the corner cases and off-by-one errors.

The repeated incrementing of the list's "pointers" by (setf p2 (cdr p2)) etc. can be coded with a recursive function instead, by calling it with (cdr p2) as an argument.

CodePudding user response:

Here is an answer based on Will Ness's nice trick. You probably can't submit this as homework. tconc comes from Interlisp, at least its name does.

(defun empty-tconc ()
  ;; make an empty accumulator for TCONC
  (cons nil nil))

(defun tconc (v into)
  ;; destructively add V to the end of the accumulator INTO, return
  ;; INTO
  (if (null (car into))
      (setf (car into) (list v)
            (cdr into) (car into))
    (setf (cdr (cdr into)) (list v)
          (cdr into) (cdr (cdr into))))
  into)

(defun tconc-value (into)
  ;; Retrieve the value of an accumulator
  (car into))

(defun shorten (l n)
  ;; shorten list L by N elements at the end
  (check-type n (integer 0))
  (shorten-loop l l n (empty-tconc)))

(defun shorten-loop (l1 l2 n into)
  (if (zerop n)
      (if (endp l2)
          (tconc-value into)
        (shorten-loop (rest l1) (rest l2) n
                      (tconc (first l1) into)))
    ;; Note (rest '()) is '() in CL, hence this abuse
    (shorten-loop l1 (rest l2) (1- n) into)))

CodePudding user response:

The solution to this problem is a tail-call recursion. That is to say, when we do each step, we call the function, reducing the count and the input list, and updating a result. When we're done, we return the result. We know that we're done because the counter is zero or the input list is exhausted.

(defun shorten (n lst &optional (res '()))
  (if (or (= n 0) (null lst))
      (reverse res)
      (shorten (1- n) (cdr lst) (cons (car lst) res))))

(shorten 5 '(1 2 3 4 5 6 7 8 9 10))    ; (1 2 3 4 5)
(shorten 1 '(1 2 3 4 5 6 7 8 9 10))    ; (1)
(shorten 11 '(1 2 3 4 5 6 7 8 9 10))   ; (1 2 3 4 5 6 7 8 9 10)
(shorten 0 '(1 2 3 4 5 6 7 8 9 10))    ; NIL
(shorten 5 '())                        ; NIL
  • Related