I've a question, how to return a list without the nth element of a given list? E.g., given list: (1 2 3 2 4 6)
, and given n = 4
, in this case the return list should be (1 2 3 4 6)
.
A simple recursive solution:
(defun remove-nth (n list)
(declare
(type (integer 0) n)
(type list list))
(if (or (zerop n) (null list))
(cdr list)
(cons (car list) (remove-nth (1- n) (cdr list)))))
This will share the common tail, except in the case where the list has n
or more elements, in which case it returns a new list with the same elements as the provided one.
DO
based version manually building the result and it was (on SBCL) 4 times faster and half consing compared to the remove-if
approach, but this is much cleaner and with the same performance... –
Tulle delete-if
instead of remove-if
? On CCL, this destructive version is an order of magnitude faster and less consing with the default optimize
settings and without further tweaks. (Tested with a list of 100k elements, dropping an element in the middle.) –
Downpipe copy-list
would neutralize the performance/consing advantage. However, if one knows what they're doing, resorting to destructive operations is a legitimate means to improve performance. I just wanted to point out another possibility that improves perfomance of my original solution by an order of magnitude by just changing a single word. –
Downpipe do
/(setf (cdr x) y)
) doesn't scale by composition. I'm no Common Lisp expert, but as the problem is stated however ("return a list without the nth element of a given list") I'd assume that a destructive operation would be surprising... but may be it's me (and I've been for example bitten in the past by sort
being destructive and not being named nsort
). –
Tulle n > length(lst)
, then what happens?? (the last sentence is wrong too). –
Molasses (1 2 3 2 6)
which is what your code produces, too. The last sentence should speak of "less than n
elements". Small stuff, off-by-1 &the like. :) –
Molasses n
is negative? (and for destructive versions, what if n
is greater than the circular list's period?) –
Molasses Using remove-if
:
(defun foo (n list)
(remove-if (constantly t) list :start (1- n) :count 1))
butlast
/nthcdr
solution (corrected):
(defun foo (n list)
(append (butlast list (1+ (- (length list) n))) (nthcdr n list)))
Or, maybe more readable:
(defun foo (n list)
(append (subseq list 0 (1- n)) (nthcdr n list)))
Using loop
:
(defun foo (n list)
(loop for elt in list
for i from 1
unless (= i n) collect elt))
butlast
like take
in the butlast
/nthcdr
solution. (Thinking along the lines of take n xs ++ drop (n+1) xs
) –
Downpipe (defun nfoo (n ls) (nconc (butlast ...) ...))
. But the check for n > (length ls)
case is missing. Only the loop
version is working then. –
Molasses (assert (<= 1 n (length list)))
, or make the implementations just return the original list. (BTW, on CCL the remove-if
and loop
versions both do the latter.) –
Downpipe loop
worked properly. –
Molasses loop
version won't terminate. I guess it means that only the simple recursive copying-sharing version of Samuel E. W. will be the right answer, after the one-off error is corrected there. –
Molasses remove-if
itself would have to be considered incorrect, since it doesn't terminate despite its given :start
and :count
(woudn't work with :end
either) parameters. Rather, I would think of circular lists as a special case. (In fact, per the standard, circular lists aren't proper lists at all.) –
Downpipe Here's an interesting approach. It replaces the nth element of a list with a new symbol and then removes that symbol from the list. I haven't considered how (in)efficient it is though!
(defun remove-nth (n list)
(remove (setf (nth n list) (gensym)) list))
(loop :for i :in '(1 2 3 2 4 6) ; the list
:for idx :from 0
:unless (= 3 idx) :collect i) ; except idx=3
;; => (1 2 3 4 6)
loop macro can be very useful and effective in terms of generated code by lisp compiler and macro expander.
Test run and apply macroexpand above code snippet.
A slightly more general function:
(defun remove-by-position (pred lst)
(labels ((walk-list (pred lst idx)
(if (null lst)
lst
(if (funcall pred idx)
(walk-list pred (cdr lst) (1+ idx))
(cons (car lst) (walk-list pred (cdr lst) (1+ idx)))))))
(walk-list pred lst 1)))
Which we use to implement desired remove-nth:
(defun remove-nth (n list)
(remove-by-position (lambda (i) (= i n)) list))
And the invocation:
(remove-nth 4 '(1 2 3 2 4 6))
Edit: Applied remarks from Samuel's comment.
remove-nth
to... something else... remove-by-position
? And defining remove-nth (defun remove-nth (n list) (remove-by-position (lambda (i) (= i n)) list)
. –
Mchail A destructive version, the original list will be modified (except when n < 1),
(defun remove-nth (n lst)
(if (< n 1) (cdr lst)
(let* ((p (nthcdr (1- n) lst))
(right (cddr p)))
(when (consp p)
(setcdr p nil))
(nconc lst right))))
That's elisp but I think those are standard lispy functions.
For all you haskellers out there, there is no need to twist your brains :)
(defun take (n l)
(subseq l 0 (min n (length l))))
(defun drop (n l)
(subseq l n))
(defun remove-nth (n l)
(append (take (- n 1) l)
(drop n l)))
My horrible elisp solution:
(defun without-nth (list n)
(defun accum-if (list accum n)
(if (not list)
accum
(accum-if (cdr list) (if (eq n 0) accum (cons (car list) accum))
(- n 1))))
(reverse (accum-if list '() n)))
(without-nth '(1 2 3) 1)
Should be easily portable to Common Lisp.
A much simpler solution will be as follows.
(defun remove-nth (n lst)
(append (subseq lst 0 (- n 1)) (subseq lst n (length lst)))
)
© 2022 - 2024 — McMap. All rights reserved.