I'm working my way through the problems in "Common Lisp: A Gentle
Introduction To Symbolic Computation" (available for download at
http://www-2.cs.cmu.edu/~dst/LispBook/index.html) and have run into some
behavior I don't understand. Exercise 11.22(d) specifies a function for
counting DNA bases in a sequence. My solution is:
(defun count-bases (strand)
"Exercise 11.22(d) from Touretzky's 'Common Lisp'."
(labels ((flatten (tree)
(cond ((null tree) nil)
((atom tree) (list tree))
(t (append (flatten (first tree)) (flatten (rest tree)))))))
(let ((base-counts '((a 0) (c 0) (g 0) (t 0)))
(single-strand (flatten strand)))
(dolist (base single-strand base-counts)
(incf (cadr (assoc base base-counts)))))))
When I run this function once, e.g.:
(count-bases '((g c) (a t) (t a) (t a) (c g)))
I get the correct answer. When I run it again, the numbers accumulate:
((A 3) (C 2) (G 2) (T 3))
((A 6) (C 4) (G 4) (T 6))
((A 9) (C 6) (G 6) (T 9))
etc.
Why isn't base-counts being reinitialized each time?
Thanks for any help,
Sean
Sean Winship wrote:
> I'm working my way through the problems in "Common Lisp: A Gentle
> Introduction To Symbolic Computation" (available for download at
> http://www-2.cs.cmu.edu/~dst/LispBook/index.html) and have run into some
> behavior I don't understand. Exercise 11.22(d) specifies a function for
> counting DNA bases in a sequence. My solution is:
>
> (defun count-bases (strand)
> "Exercise 11.22(d) from Touretzky's 'Common Lisp'."
> (labels ((flatten (tree)
> (cond ((null tree) nil)
> ((atom tree) (list tree))
> (t (append (flatten (first tree)) (flatten (rest tree)))))))
> (let ((base-counts '((a 0) (c 0) (g 0) (t 0)))
> (single-strand (flatten strand)))
> (dolist (base single-strand base-counts)
> (incf (cadr (assoc base base-counts)))))))
>
> When I run this function once, e.g.:
>
> (count-bases '((g c) (a t) (t a) (t a) (c g)))
>
> I get the correct answer. When I run it again, the numbers accumulate:
>
> ((A 3) (C 2) (G 2) (T 3))
> ((A 6) (C 4) (G 4) (T 6))
> ((A 9) (C 6) (G 6) (T 9))
> etc.
>
> Why isn't base-counts being reinitialized each time?
>
> Thanks for any help,
>
> Sean
Jim is right. Take a look at
http://www.lispworks.com/reference/HyperSpec/Body/s_quote.htm
It points out:
The consequences are undefined if literal objects (including quoted
objects) are destructively modified.
So the value of your BASE-COUNTS variable is (in your implementation)
permanently modified each time you call COUNT-BASES.
To be on the safe side you need to do something like:
(let ((base-counts (mapcar #'(lambda (elt) (list elt 0)) '(a c g t))) ...
Jim's solution is fine, but it's too much typing for me! :)
Even this won't work:
(let ((base-counts (copy-seq '((a 0) (c 0) (g 0) (t 0)))) ...
since you are modifying the sublists not the top-level list.
David Sletten
On Tue, 30 Nov 2004 22:20:52 GMT, David Sletten <·····@slytobias.com>
wrote:
>Even this won't work:
>(let ((base-counts (copy-seq '((a 0) (c 0) (g 0) (t 0)))) ...
>since you are modifying the sublists not the top-level list.
COPY-TREE (instead of COPY-SEQ) will work.
___________________
Real email address:
(substitute ··@ #\+ (substitute #\s #\! "u!enet001+nomi!tech.com"))
From: Jim Newton
Subject: Re: value not reinitialized on subsequent function calls
Date:
Message-ID: <31473eF36pl50U1@individual.net>
I think your problem is that the quote ' does
not do any memory allocation and you are destructively
mofitying the list including modifying the function itself.
You need to have some sort of list copy on base-counts
to protect the data from the destructive modification.
e.g.,
(let ((base-counts (list (list 'a 0) (list 'c 0) (list 'g 0) (list t 0)))
-jim
Sean Winship wrote:
> (let ((base-counts '((a 0) (c 0) (g 0) (t 0)))
In article <···············@individual.net>,
Jim Newton <·····@rdrop.com> wrote:
> I think your problem is that the quote ' does
> not do any memory allocation and you are destructively
> mofitying the list including modifying the function itself.
Yes. This is like modifying a string literal in C, if the OP is familiar
with that language.
> You need to have some sort of list copy on base-counts
> to protect the data from the destructive modification.
>
> e.g.,
> (let ((base-counts (list (list 'a 0) (list 'c 0) (list 'g 0) (list t 0)))
or:
(let ((base-counts (loop for base in '(a c g tee)
collecting (list base 0))))...
Not that 't is not a nice symbol. I don't know, maybe there would be no
ill consequences. Well, if you forget to quote the other three there
would likely be a compiler warning, but t would not.
btw, if loop seems too advanced:
(mapcar (lambda (base) (list base 0))
'(a c g tee))
kenny
From: Brian Downing
Subject: Re: value not reinitialized on subsequent function calls
Date:
Message-ID: <la8rd.591751$mD.383024@attbi_s02>
In article <·····························@nycmny-nntp-rdr-03-ge1.rdc-nyc.rr.com>,
Kenneth Tilton <·······@nyc.rr.com> wrote:
> btw, if loop seems too advanced:
>
> (mapcar (lambda (base) (list base 0))
> '(a c g tee))
For perversity:
(mapcar #'list '(a c g tee) '#1=(0 . #1#))
Though honestly for the original poster
(copy-tree '((a 0) (c 0) (g 0) (t 0)))
is probably best. Short and easy to understand.
-bcd
--
*** Brian Downing <bdowning at lavos dot net>