moving an element of a list to another list

Discussion of Common Lisp
vernonsolo
Posts: 4
Joined: Thu Oct 28, 2010 5:11 am

moving an element of a list to another list

Post by vernonsolo » Thu Oct 28, 2010 1:31 pm

Let's say i need to remove the nth element of the list "l" and push it at the beginning of list "out".
The best i could figure out is :

Code: Select all

(progn
  (push (nth n l) out)
  (setf l (delete (nth n l) l))
But it looks pretty inefficient to me. "(nth n l)" is evaluated twice and "(delete (nth n l) l)" doesn't sound good.
Is there a better way to do that ?

nuntius
Posts: 538
Joined: Sat Aug 09, 2008 10:44 am
Location: Newton, MA

Re: moving an element of a list to another list

Post by nuntius » Thu Oct 28, 2010 2:12 pm

Is there a better way? Yes, but you have to write it at a lower level (doing your own loop over the list, your own cdr manipulations, etc.).

If this isn't a performance issue, I would focus on getting the rest of your code working before worrying about this. At that time, the answer may be to optimize this code, or it may be to use a different data structure.

gugamilare
Posts: 406
Joined: Sat Mar 07, 2009 6:17 pm
Location: Brazil
Contact:

Re: moving an element of a list to another list

Post by gugamilare » Thu Oct 28, 2010 5:38 pm

Destructive:

Code: Select all

(if (zerop n)
    (progn (push (car l) out)
           (setf l (cdr l)))
    (let ((prevcdr (nthcdr (- n 1) l)))
      (push (cadr prevcdr) out)
      (setf (cdr prevcdr) (cddr prevcdr))))
Non-destructive:

Code: Select all

(progn
  (push (nth n l) out)
  (setf l (nconc (subseq l 0 n) (nthcdr (1+ n) l))))
I'd say that the biggest problem with that code is not preformance, but it will delete multiple copies of the same element if it is found. If that is what you want, use let to store the nth value instead of computing it twice.

vernonsolo
Posts: 4
Joined: Thu Oct 28, 2010 5:11 am

Re: moving an element of a list to another list

Post by vernonsolo » Fri Oct 29, 2010 12:33 am

Thanks Gugamilare, the first one look like what i was looking for.
I was hoping there was a standard function that could help in this kind of case. Something like "pop" but which can work in the middle of a list. But now i realize that the "one way" nature of the lists makes this difficult to do.
gugamilare wrote:
I'd say that the biggest problem with that code is not preformance, but it will delete multiple copies of the same element if it is found. If that is what you want, use let to store the nth value instead of computing it twice.
Good point ! In my case this was not an issue, i forgot to mention that all the elements of l are different. But it is sure something to be aware of.

gugamilare
Posts: 406
Joined: Sat Mar 07, 2009 6:17 pm
Location: Brazil
Contact:

Re: moving an element of a list to another list

Post by gugamilare » Fri Oct 29, 2010 4:06 am

vernonsolo wrote:Thanks Gugamilare, the first one look like what i was looking for.
I was hoping there was a standard function that could help in this kind of case. Something like "pop" but which can work in the middle of a list. But now i realize that the "one way" nature of the lists makes this difficult to do.
Haven't you learned Lisp yet? :D

Non-destructive:

Code: Select all

(defmacro pop-nth (n l)
  (with-gensyms (nvar lvar ncdr)
    `(let* ((,nvar ,n)
            (,lvar ,l)
            (,nthcdr (nthcdr ,nvar ,lvar)))
       (prog1 (car ,nthcdr)
         (setf ,l (nconc (subseq ,lvar 0 ,nvar)
                         (cdr ,nthcdr)))))))
Destructive:

Code: Select all

(defmacro npop-nth (n l)
  (with-gensyms (nvar lvar prevcdr)
    `(let ((,nvar ,n)
           (,lvar ,l))
       (if (zerop ,nvar)
           (prog1 (car ,lvar)
             (setf ,l (cdr ,lvar)))
           (let ((,prevcdr (nthcdr (- ,nvar 1) ,varl)))
             (prog1 (cadr ,prevcdr)
               (setf (cdr ,prevcdr) (cddr ,prevcdr))))))))
Note that I just wrote that, I didn't test anything, but that might be enough to get the idea :)

vernonsolo
Posts: 4
Joined: Thu Oct 28, 2010 5:11 am

Re: moving an element of a list to another list

Post by vernonsolo » Fri Oct 29, 2010 9:57 am

gugamilare wrote: Haven't you learned Lisp yet? :D
I just started few days ago :)

I tried the non-destructive one in my program, but i had problem with 'with-gensyms' so i modified it to :

Code: Select all

(defmacro pop-nth (n l)
  (let ((nvar (gensym)) (lvar (gensym)) (ncdr (gensym)))
    `(let* ((,nvar ,n)
            (,lvar ,l)
            (,ncdr (nthcdr ,nvar ,lvar)))
       (prog1 (car ,ncdr)
         (setf ,l (nconc (subseq ,lvar 0 ,nvar)
                         (cdr ,ncdr)))))))
then my ugly code is replaced by :

Code: Select all

(push (pop-nth n l) out)
and it works ! it's even like 5 or 10% faster :D

It is my first macro 8-)

Thanks for your help

PS: then i tried the destructive one and gained 20% speed from the original :ugeek:

gugamilare
Posts: 406
Joined: Sat Mar 07, 2009 6:17 pm
Location: Brazil
Contact:

Re: moving an element of a list to another list

Post by gugamilare » Fri Oct 29, 2010 11:12 am

vernonsolo wrote:PS: then i tried the destructive one and gained 20% speed from the original :ugeek:
If you try it with bigger lists, the performance gain will be even greater :)

Warren Wilkinson
Posts: 117
Joined: Tue Aug 10, 2010 11:24 pm
Location: Calgary, Alberta
Contact:

Re: moving an element of a list to another list

Post by Warren Wilkinson » Thu Nov 04, 2010 8:35 pm

I probably wouldn't write a macro for this problem, I would isolate the problem of 'stealing' an item from a list:

Code: Select all

(defmacro awhen (test &rest code)
  `(let ((it ,test))
     (when it ,@code)))

(defun steal (n list)
  (awhen (nthcdr (1- n) list)
    (prog1 (cadr it) (rplacd it (cddr it)))))
And it is used like this:

Code: Select all

(defvar *test-input* '(a b c d e f g))
(steal 1 *test-input*)   ;; Returns 'b, *test-output* is now (a c d e f g)
There is one problem --- what happens if you steal item 0 from a list? It won't work, and it cannot be made to work because steal is a utility function, and it would be unwise to hardcode it so it would setf *test-input* to (cdr *test-input*) for the zero case. However, we can write a function using steal that has this knowledge.

Code: Select all

(defvar *input* '(a b c d e))
(defvar *output* nil)

(defun steal/pop (n)
  (if (and *test-input* (zerop n))
      (setf *output* (cons (car *input*) *output*) 
	    *input* (cdr *input*))
      (and (< n (length *input*)) (push (steal n *input*) *output*))))
This method does exactly what you describe. It steals the Nth element from *input*, and pushes it onto *output*, I also made it do bounds checking so it won't do anything if N is larger than *input*. The routine is hardcoded to reference those external variables, which is ideal because its simple.

If you have a number of lists that you need to modify, and you don't want to have a customized steal routine for each one, you can use a sentinal value like so:

Code: Select all

(defvar *new-input* '(sentinal a b c d e))
By putting a sentinal value in as item 0, we have no reason to ever require a (steal 0 *new-input*). This works fine so long as we remember to perform all other operations (like length, find, position, etc) upon (cdr *new-input*) and not the full *new-input*.
Need an online wiki database? My Lisp startup http://www.formlis.com combines a wiki with forms and reports.

vernonsolo
Posts: 4
Joined: Thu Oct 28, 2010 5:11 am

Re: moving an element of a list to another list

Post by vernonsolo » Fri Nov 05, 2010 4:49 am

Thanks Warren for the suggestions, it was very interesting. I think it is always good to know more than one way to do something ;)
This "awhen" macro... I cannot count how many time i had to use this mechanism and yet i never thought of doing a macro out of it, thanks for that too :D

Warren Wilkinson
Posts: 117
Joined: Tue Aug 10, 2010 11:24 pm
Location: Calgary, Alberta
Contact:

Re: moving an element of a list to another list

Post by Warren Wilkinson » Fri Nov 05, 2010 10:12 am

The 'a' stands for anamorphic. Paul Graham's 'On Lisp' has it (and anamorphic if, cond, etc), but I think the technique is older than that still. PG also has the definition of 'with-gensyms'. I won't go into details since PG explains it in that book, which is freely available as a downloadable pdf at http://www.paulgraham.com/onlisp.html.
Need an online wiki database? My Lisp startup http://www.formlis.com combines a wiki with forms and reports.

Post Reply