ccc31807 <
cart...@gmail.com> writes:
> Okay, I'm now spending about an hour a day writing real lisp, enough
> so that the non-obvious questions come up. Below, I have copied four
> variables that I use, two functions, SUM-CDR and GOODNESS-1, and
> example executions for both players.
>
> It adds up the cdr of each sublist depending on whether the player is
> 0 (alpha) or 1 (beta) and returns the difference between the number
> of pebbles possessed by that player and the total number of pebbles.
>
> Question: Even to my unpracticed eye, this looks pretty awful. Any
> comments for improvements from a style perspective?
>
> As always, thanks, CC.
>
> --------------vars-----------------
> (defparameter list-0 '((0 . 3)(1 . 3)(2 . 3)(3 . 3)(4 . 3)(5 . 3)))
> (defparameter list-1 '((0 . 4)(1 . 0)(2 . 5)(3 . 0)(4 . 6)(5 . 3)))
> (defparameter list-2 '((0 . 0)(1 . 0)(2 . 0)(3 . 6)(4 . 2)(5 . 10)))
> (defparameter list-3 '((0 . 5)(1 . 5)(2 . 8)(3 . 0)(4 . 0)(5 . 0)))
Always use stars around the names of special variables! (defvar and
defparameter).
> -------------funcs-------------------
> (defun sum-cdr (lst &optional (sum 0))
> (cond
> ((null lst) sum)
> (t
> (incf sum (cdar lst))
> (sum-cdr (cdr lst) sum))))
1- You can name your parameter list, no need to use abbreviations.
2- You should write a docstring to each function (and each definition in
general). In particular, the docstring must tell enough about the
purpose (the specifications) of the function so that we can check
whether it does what it must do (ie. whether there are bugs!).
3- This functions is a poor choice of a function because it is too
specific. You could take each accessor in CL and all the libraries,
and write a similar sum-something function. This is ludicruous.
Instead, use (or implement if it is missing) a generalized function.
Something like:
(reduce (function +) a-list :key (function cdr))
Oops! There is already reduce, you didn't have to write sum-cdr!
> (defun goodness-1 (player board)
> (let* ((len (length board))
> (half (ash len -1))
No. This is not C. (half (/ len 2))
Compilers are smart enough to optimize it if they deem it fit.
> (total-pebbles (sum-cdr board))
So, (reduce …)
> (alpha (subseq board 0 half))
> (beta (subseq board half)))
Now, the problem is that you're walking the list three times. (Once for
length, once for alpha and once for beta (even if for alpha it's only
n/2, it still counts as O(n)).
You could write a function that split the list in two in n steps.
> (format t "board: ~a, player ~a~% total-pebbles ~a~%" board player
> total-pebbles)
No. Unless it's for debugging, do not mix I/O (and in general, side
effects) with normal functions.
> (if (zerop player)
> (- (sum-cdr alpha) (ash total-pebbles -1))
> (- (sum-cdr beta) (ash total-pebbles -1)))))
See above.
Now since you're using sum-cdr thrice in this function, you could wrap
the reduce form in a function on the condition you're able to find it a
meaningful name, domain-wise. If you may need this function outside of
goodness-1, then you may keep a defun form, otherwise you could use flet
(or labels it if needed).
So, assuming it may be needed elsewhere:
;; Let's introduce some abstraction:
(defun make-hole (number pebbles)
"Return a new hole, identified by its number, and containing the
indicated number of pebbles."
(cons number pebbles))
(defun hole-number (hole)
"Return the number of the hole."
(car hole))
(defun pebbles-in-hole (hole)
"Return the number of pebbles in the hole."
(cdr hole))
;; And don't forget test cases:
(loop
:for pebbles :below 5
:do (assert (= pebbles (pebbles-in-hole (make-hole 0 pebbles)))))
(loop
:for number :below 5
:do (assert (= number (hole-number (make-hole number 0)))))
(defun count-pebbles (board)
"Return the total number of pebbles on the board.
BOARD: a list of holes."
(reduce (function +) board :key (function pebbles-in-hole)))
(assert (zerop (count-pebbles '()))) ; empty board has no pebbles
(assert (zerop (count-pebbles (list (make-hole 1 0))))) ; empty hole has no pebbles
(assert (= 1 (count-pebbles (list (make-hole 1 1) (make-hole 2 0)))))
(assert (= 1 (count-pebbles (list (make-hole 1 0) (make-hole 2 1)))))
(assert (= 2 (count-pebbles (list (make-hole 1 1) (make-hole 2 1)))))
(defun split-list-in-two (list)
"Return: a fresh list containing the first half of the element, and
the tail of the LIST that contains the other half."
(loop
:for hare = list :then (cddr hare)
:for turtle = list :then (cdr turtle)
:while hare
:collect (car turtle) :into first-part
:finally (return (values first-part turtle))))
(assert (equal (multiple-value-list (split-list-in-two '())) '(nil nil)))
(assert (equal (multiple-value-list (split-list-in-two '(1))) '((1) nil)))
(assert (equal (multiple-value-list (split-list-in-two '(1 2))) '((1) (2))))
(assert (equal (multiple-value-list (split-list-in-two '(1 2 3))) '((1 2) (3))))
(assert (equal (multiple-value-list (split-list-in-two '(1 2 3 4))) '((1 2) (3 4))))
(assert (equal (multiple-value-list (split-list-in-two '(1 2 3 4 5))) '((1 2 3) (4 5))))
(assert (equal (multiple-value-list (split-list-in-two '(1 2 3 4 5 6))) '((1 2 3) (4 5 6))))
(assert (equal (multiple-value-list (split-list-in-two '(1 2 3 4 5 6 7))) '((1 2 3 4) (5 6 7))))
(defun goodness-1 (player board)
"Return a goodness measure of the BOARD for the PLAYER."
(multiple-value-bind (alpha-part beta-part) (split-list-in-two board)
(- (count-pebbles (if (zerop player)
alpha-part
beta-part))
(/ (count-pebbles board) 2))))
(assert (= 0 (goodness-1 0 (list (make-hole 1 1) (make-hole 2 1)))))
(assert (= 1/2 (goodness-1 0 (list (make-hole 1 2) (make-hole 2 0) (make-hole 3 1) (make-hole 4 0)))))
(assert (= -1/2 (goodness-1 1 (list (make-hole 1 2) (make-hole 2 0) (make-hole 3 1) (make-hole 4 0)))))
Oh, and if you want some I/O:
(defun print-board (board)
(format t "board: ~a~% total-pebbles ~a~%" board (count-pebbles board))
(values))
(defparameter *list-3* '((0 . 5)(1 . 5)(2 . 8)(3 . 0)(4 . 0)(5 . 0)))
(print-board *list-3*)
board: ((0 . 5) (1 . 5) (2 . 8) (3 . 0) (4 . 0) (5 . 0))
total-pebbles 18
--
__Pascal Bourguignon__
http://www.informatimago.com/
A bad day in () is better than a good day in {}.