|
| 1 | +;;;; Graham scan implementation in Common Lisp |
| 2 | + |
| 3 | +(defstruct (point (:constructor make-point (x y))) x y) |
| 4 | + |
| 5 | +(defun ccw (p1 p2 p3) |
| 6 | + "Determines if a turn between three points is counterclockwise" |
| 7 | + (- |
| 8 | + (* |
| 9 | + (- (point-y p2) (point-y p1)) |
| 10 | + (- (point-x p3) (point-x p1))) |
| 11 | + (* |
| 12 | + (- (point-y p3) (point-y p1)) |
| 13 | + (- (point-x p2) (point-x p1))))) |
| 14 | + |
| 15 | +(defun atan2 (y x) |
| 16 | + "Calculates the angle of a point in the euclidean plane in radians" |
| 17 | + (cond |
| 18 | + ((> x 0) (atan y x)) |
| 19 | + ((and (< x 0) (>= y 0)) (+ (atan y x) pi)) |
| 20 | + ((and (< x 0) (< y 0)) (- (atan y x) pi)) |
| 21 | + ((and (eql x 0) (> y 0)) (/ pi 2)) |
| 22 | + ((and (eql x 0) (< y 0)) (- (/ pi 2))) |
| 23 | + ;; The -1 signifies an exception and is usefull later for sorting by the polar angle |
| 24 | + ((and (eql x 0) (eql y 0)) -1))) |
| 25 | + |
| 26 | +(defun polar-angle (ref point) |
| 27 | + "Returns the polar angle from a point relative to a reference point" |
| 28 | + (atan2 (- (point-y point) (point-y ref)) (- (point-x point) (point-x ref)))) |
| 29 | + |
| 30 | +(defun lowest-point (gift) |
| 31 | + "Returns the lowest point of a gift" |
| 32 | + (reduce |
| 33 | + (lambda (p1 p2) |
| 34 | + (if (< (point-y p1) (point-y p2)) p1 p2)) |
| 35 | + gift)) |
| 36 | + |
| 37 | +(defun graham-scan (gift) |
| 38 | + "Finds the convex hull of a distribution of points with a graham scan" |
| 39 | + ;; An empty list evaluates to false (nil) and a non-empty list evaluates to true (t). |
| 40 | + ;; We can therefore use 'gift' instead of '(> (length gift) 0)'. |
| 41 | + (if gift |
| 42 | + (labels ((wrap (sorted-points hull) |
| 43 | + (if sorted-points |
| 44 | + ;; This covers the case where the hull has one or more element. |
| 45 | + ;; We aren't concerned about the hull being empty, because then the gift must |
| 46 | + ;; also be empty and this function is never given an empty gift. |
| 47 | + (if (rest hull) |
| 48 | + (if (<= (ccw (first sorted-points) (first hull) (second hull)) 0) |
| 49 | + (wrap sorted-points (rest hull)) |
| 50 | + (wrap (rest sorted-points) (cons (first sorted-points) hull))) |
| 51 | + (wrap (rest sorted-points) (list (first sorted-points) (first hull)))) |
| 52 | + hull))) |
| 53 | + ;; Because 'sort' shuffles things around destructively, graham-scan is also destructive. But |
| 54 | + ;; since the order of the points is generally not important, this shouldn't cause a problem. |
| 55 | + (let* ((lowest (lowest-point gift)) |
| 56 | + (sorted (sort gift #'< :key (lambda (p) (polar-angle lowest p))))) |
| 57 | + (wrap sorted (list lowest)))) |
| 58 | + nil)) |
| 59 | + |
| 60 | +(defvar gift |
| 61 | + (map |
| 62 | + 'list |
| 63 | + (lambda (e) (apply #'make-point e)) |
| 64 | + '((-5 2) (5 7) (-6 -12) (-14 -14) (9 9) |
| 65 | + (-1 -1) (-10 11) (-6 15) (-6 -8) (15 -9) |
| 66 | + (7 -7) (-2 -9) (6 -5) (0 14) (2 8)))) |
| 67 | + |
| 68 | +;; This should print out the following: |
| 69 | +;; (#S(POINT :X -10 :Y 11) #S(POINT :X -6 :Y 15) #S(POINT :X 0 :Y 14) |
| 70 | +;; #S(POINT :X 9 :Y 9) #S(POINT :X 7 :Y -7) #S(POINT :X -6 :Y -12)) |
| 71 | +(print (graham-scan gift)) |
0 commit comments