From 2bd895ea06b72d064e3ccc48070ca425bde4c0c4 Mon Sep 17 00:00:00 2001 From: Eric Berquist Date: Tue, 26 Oct 2021 22:11:50 -0400 Subject: [PATCH 1/5] First Emacs Lisp implementation of Graham scan --- .../graham_scan/code/elisp/graham-scan.el | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 contents/graham_scan/code/elisp/graham-scan.el diff --git a/contents/graham_scan/code/elisp/graham-scan.el b/contents/graham_scan/code/elisp/graham-scan.el new file mode 100644 index 000000000..8170ae7c8 --- /dev/null +++ b/contents/graham_scan/code/elisp/graham-scan.el @@ -0,0 +1,61 @@ +(require 'cl-seq) + +(defun -concat (&rest lists) + "Return a new list with the concatenation of the elements in the supplied LISTS." + (declare (pure t) (side-effect-free t)) + (apply 'append lists)) + +(defun -snoc (list elem &rest elements) + "Append ELEM to the end of the list. + +This is like `cons', but operates on the end of list. + +If ELEMENTS is non nil, append these to the list as well." + (-concat list (list elem) elements)) + +(defun nthrev (n lst) + "Return the Nth element of LST from the end." + ;; (car (nthcdr n (reverse lst))) + (nth (- (length lst) (1+ n)) lst)) + +(defun is-ccw (a b c) + (>= (* (- (nth 1 c) (nth 1 a)) (- (nth 0 b) (nth 0 a))) + (* (- (nth 1 b) (nth 1 a)) (- (nth 0 c) (nth 0 a))))) + +(defun polar-angle (ref point) + (atan (- (nth 1 point) (nth 1 ref)) (- (nth 0 point) (nth 0 ref)))) + +(require 'dash) + +(defun graham-scan (initial-gift) + (let* ((gift (cl-remove-duplicates initial-gift)) + ;; this is /only/ to get the starting point + (min-sorted-gift (sort gift (lambda (p1 p2) (< (nth 1 p1) (nth 1 p2))))) + (start (car min-sorted-gift)) + (trimmed-gift (cdr min-sorted-gift)) + (points (sort trimmed-gift (lambda (p1 p2) (< (polar-angle start p1) + (polar-angle start p2))))) + (hull (list start (car points) (cadr points)))) + (dolist (point (cddr points)) + (while (not (is-ccw (nthrev 1 hull) (nthrev 0 hull) point)) + (setq hull (-remove-at (1- (length hull)) hull))) + (setq hull (-snoc hull point))) + hull)) + +(princ + (graham-scan + '((-5 2) + (5 7) + (-6 -12) + (-14 -14) + (9 9) + (-1 -1) + (-10 11) + (-6 15) + (-6 -8) + (15 -9) + (7 -7) + (-2 -9) + (6 -5) + (0 14) + (2 8)))) From da294f710d8cccbc93b4e20ddb7ebc50c8344b74 Mon Sep 17 00:00:00 2001 From: Eric Berquist Date: Tue, 26 Oct 2021 22:16:40 -0400 Subject: [PATCH 2/5] Simpler snoc --- contents/graham_scan/code/elisp/graham-scan.el | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/contents/graham_scan/code/elisp/graham-scan.el b/contents/graham_scan/code/elisp/graham-scan.el index 8170ae7c8..5e3c4bdbb 100644 --- a/contents/graham_scan/code/elisp/graham-scan.el +++ b/contents/graham_scan/code/elisp/graham-scan.el @@ -1,17 +1,12 @@ (require 'cl-seq) -(defun -concat (&rest lists) - "Return a new list with the concatenation of the elements in the supplied LISTS." - (declare (pure t) (side-effect-free t)) - (apply 'append lists)) - -(defun -snoc (list elem &rest elements) +(defun snoc (list elem) "Append ELEM to the end of the list. This is like `cons', but operates on the end of list. -If ELEMENTS is non nil, append these to the list as well." - (-concat list (list elem) elements)) +Adapted from dash.el." + (append list (list elem))) (defun nthrev (n lst) "Return the Nth element of LST from the end." @@ -39,7 +34,7 @@ If ELEMENTS is non nil, append these to the list as well." (dolist (point (cddr points)) (while (not (is-ccw (nthrev 1 hull) (nthrev 0 hull) point)) (setq hull (-remove-at (1- (length hull)) hull))) - (setq hull (-snoc hull point))) + (setq hull (snoc hull point))) hull)) (princ From 7cc5b4281ddab2dc2ab93d6cfba756b9b09be4ae Mon Sep 17 00:00:00 2001 From: Eric Berquist Date: Tue, 26 Oct 2021 22:35:27 -0400 Subject: [PATCH 3/5] Remove stray comment --- contents/graham_scan/code/elisp/graham-scan.el | 1 - 1 file changed, 1 deletion(-) diff --git a/contents/graham_scan/code/elisp/graham-scan.el b/contents/graham_scan/code/elisp/graham-scan.el index 5e3c4bdbb..24117d7a0 100644 --- a/contents/graham_scan/code/elisp/graham-scan.el +++ b/contents/graham_scan/code/elisp/graham-scan.el @@ -10,7 +10,6 @@ Adapted from dash.el." (defun nthrev (n lst) "Return the Nth element of LST from the end." - ;; (car (nthcdr n (reverse lst))) (nth (- (length lst) (1+ n)) lst)) (defun is-ccw (a b c) From 9fa8bad3b0ea9bb344abfde7604e2ddb41bdd175 Mon Sep 17 00:00:00 2001 From: Eric Berquist Date: Tue, 26 Oct 2021 22:44:56 -0400 Subject: [PATCH 4/5] Remove dependence on dash.el, add more docstrings --- contents/graham_scan/code/elisp/graham-scan.el | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contents/graham_scan/code/elisp/graham-scan.el b/contents/graham_scan/code/elisp/graham-scan.el index 24117d7a0..2cb93c015 100644 --- a/contents/graham_scan/code/elisp/graham-scan.el +++ b/contents/graham_scan/code/elisp/graham-scan.el @@ -13,17 +13,18 @@ Adapted from dash.el." (nth (- (length lst) (1+ n)) lst)) (defun is-ccw (a b c) + "Determines if a turn between three points is counterclockwise." (>= (* (- (nth 1 c) (nth 1 a)) (- (nth 0 b) (nth 0 a))) (* (- (nth 1 b) (nth 1 a)) (- (nth 0 c) (nth 0 a))))) (defun polar-angle (ref point) + "Returns the polar angle from a point relative to a reference point" (atan (- (nth 1 point) (nth 1 ref)) (- (nth 0 point) (nth 0 ref)))) -(require 'dash) - (defun graham-scan (initial-gift) + "Finds the convex hull of a distribution of points with a Graham scan." (let* ((gift (cl-remove-duplicates initial-gift)) - ;; this is /only/ to get the starting point + ;; This is /only/ to get the starting point. (min-sorted-gift (sort gift (lambda (p1 p2) (< (nth 1 p1) (nth 1 p2))))) (start (car min-sorted-gift)) (trimmed-gift (cdr min-sorted-gift)) @@ -32,7 +33,7 @@ Adapted from dash.el." (hull (list start (car points) (cadr points)))) (dolist (point (cddr points)) (while (not (is-ccw (nthrev 1 hull) (nthrev 0 hull) point)) - (setq hull (-remove-at (1- (length hull)) hull))) + (setq hull (reverse (cdr (reverse hull))))) (setq hull (snoc hull point))) hull)) From edeb7d2c2202ab8619f84abd4dc4d839b33c376c Mon Sep 17 00:00:00 2001 From: Eric Berquist Date: Tue, 26 Oct 2021 22:51:37 -0400 Subject: [PATCH 5/5] Add code sections to text --- contents/graham_scan/graham_scan.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contents/graham_scan/graham_scan.md b/contents/graham_scan/graham_scan.md index 586d209db..83c03c546 100644 --- a/contents/graham_scan/graham_scan.md +++ b/contents/graham_scan/graham_scan.md @@ -32,6 +32,8 @@ We can find whether a rotation is counter-clockwise with trigonometric functions [import:18-20, lang="cpp"](code/c++/graham_scan.cpp) {% sample lang="coco" %} [import:4-8, lang="coconut"](code/coconut/graham_scan.coco) +{%sample lang="elisp" %} +[import:15-22, lang="elisp"](code/elisp/graham-scan.el) {% endmethod %} If the output of this function is 0, the points are collinear. @@ -66,6 +68,8 @@ In the end, the code should look something like this: [import:26-62, lang="cpp"](code/c++/graham_scan.cpp) {% sample lang="coco" %} [import:17-30, lang="coconut"](code/coconut/graham_scan.coco) +{%sample lang="elisp" %} +[import:24-38, lang="elisp"](code/elisp/graham-scan.el) {% endmethod %} ### Bibliography @@ -95,6 +99,8 @@ In the end, the code should look something like this: [import, lang="cpp"](code/c++/graham_scan.cpp) {%sample lang="coco" %} [import, lang="coconut"](code/coconut/graham_scan.coco) +{%sample lang="elisp" %} +[import, lang="elisp"](code/elisp/graham-scan.el) {% endmethod %}