tag:blogger.com,1999:blog-12983281.post-92130413076812326712007-09-12T11:35:00.000-07:002007-09-12T11:40:08.642-07:00'Programming Collective Intelligence' in Common Lisp, Chapter 2Like many others, I've been reading Toby Segaran's <a href="http://www.amazon.com/Programming-Collective-Intelligence-Building-Applications/dp/0596529325/ref=pd_bbs_sr_1/103-3854821-2635829?ie=UTF8&s=books&qid=1189621369&sr=8-1">Programming Collective Intelligence</a>. Toby's examples are in Python. Inspired by loucal's <a href="http://loucal.net/index.php?title=programming_collective_intelligence_with&more=1&c=1&tb=1&pb=1">posting of code examples in Ruby</a>, I've decided to put up my own Common Lisp examples. These are from Chapter 2, going up to page 15, "Ranking the Critics".<br /><br />In order to just have the recommendations in the file, I used assoc lists instead of hashes. One place where Python (and Ruby) has it over Lisp is in hash syntax, just <code>critics[person]</code> instead of <code>(gethash person critics)</code> or <code>(cdr (assoc person critics :test #'equalp))</code>. I made a 'critics' function to keep down the verbosity. Is there a good way to change the syntax for hash lookup?<br /><br />The other big difference is my use of <code>mapcar</code> and <code>reduce</code> everywhere instead of Python's list comprehensions, with the occaisional <code>intersection</code> thrown in.<br /><br />If the Python source code gets posted, I may try some benchmarks with later examples.<br /><br /><pre><code><br />(defparameter *RECOMMENDATIONS* <br /> '(<br /> ("Lisa Rose" . (("Lady in the Water" . 2.5) ("Snakes on a Plane" . 3.5) ("Just My Luck" . 3.0) <br /> ("Superman Returns" . 3.5) ("You, Me and Dupree" . 2.5) ("The Night Listener" . 3.0)))<br /> ("Gene Seymour" . (("Lady in the Water" . 3.0) ("Snakes on a Plane" . 3.5) ("Just My Luck" . 1.5) <br /> ("Superman Returns" . 5.0) ("The Night Listener" . 3.0) ("You, Me and Dupree" . 3.5)))<br /> ("Michael Phillips" . (("Lady in the Water" . 2.5) ("Snakes on a Plane" . 3.0) <br /> ("Superman Returns" . 3.5) ("The Night Listener" . 4.0)))<br /> ("Claudia Puig" . (("Snakes on a Plane" . 3.5) ("Just My Luck" . 3.0) ("The Night Listener" . 4.5) <br /> ("Superman Returns" . 4.0) ("You, Me and Dupree" . 2.5)))<br /> ("Mick LaSalle" . (("Lady in the Water" . 3.0) ("Snakes on a Plane" . 4.0) ("Just My Luck" . 2.0) <br /> ("Superman Returns" . 3.0) ("The Night Listener" . 3.0) ("You, Me and Dupree" . 2.0)))<br /> ("Jack Matthews" . (("Lady in the Water" . 3.0) ("Snakes on a Plane" . 4.0) ("The Night Listener" . 3.0) <br /> ("Superman Returns" . 5.0) ("You, Me and Dupree" . 3.5)))<br /> ("Toby" . (("Snakes on a Plane" . 4.5) ("You, Me and Dupree" . 1.0) <br /> ("Superman Returns" . 4.0)))))<br /><br /><br />(defun critics (reviewer &optional movie)<br /> (labels ((get-movie (ms m)<br /> (cdr (assoc m ms :test #'equalp))))<br /> (let ((movies (cdr (assoc reviewer *RECOMMENDATIONS* :test #'equalp))))<br /> (if movie (get-movie movies movie) movies))))<br /><br />(defun similar (person1 person2 distance)<br /> (let* ((movies1 (critics person1))<br /> (movies2 (critics person2))<br /> (common-movies (mapcar #'car (intersection movies1 movies2 <br /> :test #'(lambda (x y) (equalp (car x) (car y)))))))<br /> (if (null common-movies)<br /> nil<br /> (funcall distance person1 person2 common-movies))))<br /><br />(defun euclidean-distance (person1 person2 common-movies)<br /> (let* ((sum-of-squares (reduce #'+ (mapcar <br /> #'(lambda (cm) <br /> (expt (- (critics person1 cm) (critics person2 cm)) 2)) <br /> common-movies)))<br /> (distance (/ 1 (1+ sum-of-squares))))<br /> distance))<br /><br />(defun sim-distance (person1 person2)<br /> (similar person1 person2 #'euclidean-distance))<br /><br /><br />(defun pearson-distance (person1 person2 common-movies)<br /> (let* ((n (length common-movies))<br /> (scores1 (mapcar #'(lambda (x) (critics person1 x)) common-movies))<br /> (scores2 (mapcar #'(lambda (x) (critics person2 x)) common-movies))<br /> (sum1 (reduce #'+ scores1))<br /> (sum2 (reduce #'+ scores2))<br /> (sum1-sq (reduce #'+ (mapcar #'(lambda (x) (* x x)) scores1)))<br /> (sum2-sq (reduce #'+ (mapcar #'(lambda (x) (* x x)) scores2)))<br /> (psum (reduce #'+ (mapcar #'* scores1 scores2)))<br /> (num (- psum (/ (* sum1 sum2) n)))<br /> (den (sqrt (* (- sum1-sq (/ (expt sum1 2) n)) (- sum2-sq (/ (expt sum2 2) n))))))<br /> (if (zerop den) 0 (/ num den))))<br /><br />(defun sim-pearson (person1 person2)<br /> (similar person1 person2 #'pearson-distance))<br /> <br />(defun top-matches (person &optional (n 5) (similarity #'sim-pearson))<br /> (let* ((scores (mapcar #'(lambda (x) (cons (funcall similarity person x) x)) <br /> (remove-if #'(lambda (x) (equalp x person)) (mapcar #'car *RECOMMENDATIONS*))))<br /> (sorted-scores (sort scores #'> :key #'car))<br /> (len (length sorted-scores)))<br /> (if (<= len n)<br /> sorted-scores<br /> (butlast sorted-scores (- len n)))))<br /><br /></code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/12983281-9213041307681232671?l=i-need-closures.blogspot.com'/></div>Richard Cookhttp://www.blogger.com/profile/11838741004941594394noreply@blogger.com22