2

I have a list that is used within a loop, and on each iteration I apply a function that will alter the list permanently (popping and adding elements). The problem is, the original list is never changed whenever it is nil. How may I solve this problem?. My code is shown below

(defun looping-func ()
    (let ((queue '(2)))
          (loop while (not (null queue)) do
            (let (  (num (pop queue)))
                (if (oddp num)
                    (format t "~%~A success" num)
                    (progn (format t "~%fail")
                           (add-to-list (1+ num) queue)))))))

(defun add-to-list (elem l)
    (nconc l (list elem)))

The code works as intended if the list contains more than 1 element. if it contains exactly 1 element, once that element is popped and the list becomes nil, the applied changes aren't permanent to the list anymore. I suppose this is because of how nconc is defined, if the first argument is nil, just return the second one without any alteration. Any ideas on how to go about this?

PS: I know the code above is useless, but I am using the same concept for a school project that I unfortunately can't post code for.

turingcomplete
  • 2,128
  • 3
  • 16
  • 25
  • 3
    You shouldn't use destructive modification functions like `nconc` on literal lists -- modifying literals results in undefined behavior. Change the initialization of `queue` to `(list 2)`. – Barmar Oct 19 '12 at 03:14

3 Answers3

3

Change

(add-to-list (1+ num) queue)

to

(setq queue (add-to-list (1+ num) queue))

You can't "extend" nil with nconc

(nconc nil . lists)

is equivalent to

(nconc . lists)

so, you need to put the result of add-to-list in queue

Doug Currie
  • 40,708
  • 1
  • 95
  • 119
  • That did the trick good man :). I've been staying away from setf and setq inside function because I thought they'd create global variables, and I didn't want that. I must be mistaken, I shall read on those again. Thanks again. – turingcomplete Oct 19 '12 at 03:58
  • Looks like setf would do the same thing, and its more recommended to use. – turingcomplete Oct 19 '12 at 05:35
  • 2
    Both `setf` and `setq` of a previously undefined variable MAY cause a globally visible dynamic variable to come into existence. Not a problem when variables already "exist". – Vatine Oct 19 '12 at 10:10
3

Don't add elements to the end of a list.

Never.

Lists in Lisp are designed in a way that adding an element to the head is cheap. Adding to the end is potentially costly.

To implement a LIFO queue you need a different implementation.

Don't alter constant literal data in source code during runtime.

Indent your code properly.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • @turingcomplete: a) Adding to the front? Then reversing the list? No, that's equally bad. It's equally slow. b) the consequences are undefined in Common Lisp c) just use the editor to indent Lisp code. – Rainer Joswig Oct 20 '12 at 20:58
  • Thanks a lot for the help. I suppose its better to implement a list structure with a pointer to the end of the list as well? – turingcomplete Oct 20 '12 at 21:02
1

Because I assumed this to be an exercise, here's an example, which you shouldn't use in your daily practice, you should use push macro, which probably does something similar to it:

(defmacro push-example (item list)
  (let ((the-list list))                ; we do this to prevent
                                        ; multiple evaluations
                                        ; of the `list' argument
  `(setq ,the-list (cons ,item ,the-list))))

(defparameter *test* nil)

(push-example 'foo *test*) ;; (foo)
*test* ;; (foo)

While you didn't ask for a macro (you asked for the function), Doug's answer is technically more correct, this illustrates how you could have done it using code generation through macro. Notice how this is basically doing the same thing as your function does, except that it can encapsulate the call to setq you would have to make otherwise.