Sysop: | Amessyroom |
---|---|
Location: | Fayetteville, NC |
Users: | 23 |
Nodes: | 6 (0 / 6) |
Uptime: | 52:43:54 |
Calls: | 583 |
Files: | 1,139 |
D/L today: |
179 files (27,921K bytes) |
Messages: | 111,617 |
sean wrote:
Hi c.l.l
I'm a newbie working through Graham's ACL with the help of Chris
Riesbeck's online annotations and Lisp Critic tool. The latter always scolds me when I accumulate values in the body of a dolist using setf
and friends, telling me to use 'do' instead. Trouble is, I can't see a reasonable way to to do this transformation in general.
Here's an example (which started life as Graham's 'random-next'
Figure 8.3 ACL):
;; 'choices' is an alist, not empty, whose entries are pairs
;; (symbol . weight). Make a weighted random choice of symbol.
(defun weight-sum (choices)
(reduce #'+ choices :key #'cdr))
;; Graham has a snippet similar to this.
;; Lisp Critic thinks that 'i' should be updated in a 'do'.
(defun weighted-roulette-choice (choices)
(let ((i (random (weight-sum choices))))
(dolist (pair choices)
(when (minusp (decf i (cdr pair)))
(return (car pair))))))
;; Here's an equivalent 'do'. Lisp Critic still hates the setq of
;; course, but I need the new value of 'pair' when updating 'i'.
(defun weighted-roulette-choice (choices)
(do ((l choices (cdr l))
(pair nil)
(i (random (weight-sum choices)) (- i (cdr pair))))
((minusp i) (car pair))
(setq pair (car l))))
;; I can get rid of the assignment at the cost of using do* and an extra
;; variable. Lisp Critic is happy.
(defun weighted-roulette-choice (choices)
(do* ((l choices (cdr l))
(lastpair nil pair)
(pair (car l) (car l))
(i (random (weight-sum choices)) (- i (cdr lastpair))))
((< i (cdr pair)) (car pair))))
To my naive newbie eyes, this is unreadable compared to the 'dolist' version. However, both the assignment in the body and the early return seem tailor-made for 'do'. Is there some idiomatic way to write such
a dolist as a do?
In the end I used a cowardly 'loop':
(defun weighted-roulette-choice (choices)
(let ((roulette-spin (random (weight-sum choices))))
(loop for pair in choices
sum (cdr pair) into cumulative-weight
until (> cumulative-weight roulette-spin)
finally (return (car pair)))))
I chickened out with:
(defun weighted-roulette-choice (choices)
(loop with i = (random (weight-sum choices))
for (choice . weight) in choices
when (minusp (decf i weight))
return choice))
Exactly half the parentheses of PG's version! Note that IMHO the right
name for a variable (choice vs pair) is the semantics of its content,
not a doc string for its representation. Fixing i left as an exercise.
Ken Tilton wrote:
sean wrote:
Hi c.l.l
I'm a newbie working through Graham's ACL with the help of Chris Riesbeck's online annotations and Lisp Critic tool. The latter always scolds me when I accumulate values in the body of a dolist using setf
and friends, telling me to use 'do' instead. Trouble is, I can't see a reasonable way to to do this transformation in general.
Here's an example (which started life as Graham's 'random-next'
Figure 8.3 ACL):
;; 'choices' is an alist, not empty, whose entries are pairs
;; (symbol . weight). Make a weighted random choice of symbol.
(defun weight-sum (choices)
(reduce #'+ choices :key #'cdr))
;; Graham has a snippet similar to this.
;; Lisp Critic thinks that 'i' should be updated in a 'do'.
(defun weighted-roulette-choice (choices)
(let ((i (random (weight-sum choices))))
(dolist (pair choices)
(when (minusp (decf i (cdr pair)))
(return (car pair))))))
;; Here's an equivalent 'do'. Lisp Critic still hates the setq of
;; course, but I need the new value of 'pair' when updating 'i'.
(defun weighted-roulette-choice (choices)
(do ((l choices (cdr l))
(pair nil)
(i (random (weight-sum choices)) (- i (cdr pair))))
((minusp i) (car pair))
(setq pair (car l))))
;; I can get rid of the assignment at the cost of using do* and an extra ;; variable. Lisp Critic is happy.
(defun weighted-roulette-choice (choices)
(do* ((l choices (cdr l))
(lastpair nil pair)
(pair (car l) (car l))
(i (random (weight-sum choices)) (- i (cdr lastpair))))
((< i (cdr pair)) (car pair))))
To my naive newbie eyes, this is unreadable compared to the 'dolist' version. However, both the assignment in the body and the early return seem tailor-made for 'do'. Is there some idiomatic way to write such
a dolist as a do?
In the end I used a cowardly 'loop':
(defun weighted-roulette-choice (choices)
(let ((roulette-spin (random (weight-sum choices))))
(loop for pair in choices
sum (cdr pair) into cumulative-weight
until (> cumulative-weight roulette-spin)
finally (return (car pair)))))
I chickened out with:
(defun weighted-roulette-choice (choices)
(loop with i = (random (weight-sum choices))
for (choice . weight) in choices
when (minusp (decf i weight))
return choice))
Exactly half the parentheses of PG's version! Note that IMHO the right
name for a variable (choice vs pair) is the semantics of its content,
not a doc string for its representation. Fixing i left as an exercise.
Gauche Scheme
(use srfi-27) ;; random-integer
(define (weight-sum choices)
(apply + (map cdr choices)))
(define (weighted-roulette-choice choices)
(let1 r (random-integer (weight-sum choices))
(car
(find (lambda(x) (negative? (dec! r (cdr x)))) choices))))
Testing:
(let1 h (make-hash-table)
(dotimes (9888)
(hash-table-update! h
(weighted-roulette-choice '((a . 2)(b . 5)(c . 2)))
(cut + 1 <>) 0))
(hash-table->alist h))
((c . 2122) (b . 5480) (a . 2286))
Ken Tilton wrote:
sean wrote:
Hi c.l.l
I'm a newbie working through Graham's ACL with the help of Chris Riesbeck's online annotations and Lisp Critic tool. The latter always scolds me when I accumulate values in the body of a dolist using setf
and friends, telling me to use 'do' instead. Trouble is, I can't see a reasonable way to to do this transformation in general.
Here's an example (which started life as Graham's 'random-next'
Figure 8.3 ACL):
;; 'choices' is an alist, not empty, whose entries are pairs
;; (symbol . weight). Make a weighted random choice of symbol.
(defun weight-sum (choices)
(reduce #'+ choices :key #'cdr))
;; Graham has a snippet similar to this.
;; Lisp Critic thinks that 'i' should be updated in a 'do'.
(defun weighted-roulette-choice (choices)
(let ((i (random (weight-sum choices))))
(dolist (pair choices)
(when (minusp (decf i (cdr pair)))
(return (car pair))))))
;; Here's an equivalent 'do'. Lisp Critic still hates the setq of
;; course, but I need the new value of 'pair' when updating 'i'.
(defun weighted-roulette-choice (choices)
(do ((l choices (cdr l))
(pair nil)
(i (random (weight-sum choices)) (- i (cdr pair))))
((minusp i) (car pair))
(setq pair (car l))))
;; I can get rid of the assignment at the cost of using do* and an extra ;; variable. Lisp Critic is happy.
(defun weighted-roulette-choice (choices)
(do* ((l choices (cdr l))
(lastpair nil pair)
(pair (car l) (car l))
(i (random (weight-sum choices)) (- i (cdr lastpair))))
((< i (cdr pair)) (car pair))))
To my naive newbie eyes, this is unreadable compared to the 'dolist' version. However, both the assignment in the body and the early return seem tailor-made for 'do'. Is there some idiomatic way to write such
a dolist as a do?
In the end I used a cowardly 'loop':
(defun weighted-roulette-choice (choices)
(let ((roulette-spin (random (weight-sum choices))))
(loop for pair in choices
sum (cdr pair) into cumulative-weight
until (> cumulative-weight roulette-spin)
finally (return (car pair)))))
I chickened out with:
(defun weighted-roulette-choice (choices)
(loop with i = (random (weight-sum choices))
for (choice . weight) in choices
when (minusp (decf i weight))
return choice))