diff --git a/contents/graham_scan/code/nim/.gitignore b/contents/graham_scan/code/nim/.gitignore new file mode 100644 index 000000000..c69e8da45 --- /dev/null +++ b/contents/graham_scan/code/nim/.gitignore @@ -0,0 +1 @@ +graham diff --git a/contents/graham_scan/code/nim/graham.nim b/contents/graham_scan/code/nim/graham.nim new file mode 100644 index 000000000..01709a0d0 --- /dev/null +++ b/contents/graham_scan/code/nim/graham.nim @@ -0,0 +1,65 @@ +from algorithm import sort, sorted +from math import arctan2 +from sequtils import deduplicate, map, toSeq +import sugar + +type Point[T: SomeNumber] = tuple[x, y: T] + +func tup_to_point[T](t: (T, T)): Point[T] = + (x: t[0], y: t[1]) + +func cross_product[T](p1, p2, p3: Point[T]): T = + ## Form the cross product of three points. If the result is + ## - zero, the points are collinear. + ## - positive, the points form a counter-clockwise "left" turn. + ## - negative, the points form a clockwise "right" turn. + (p3.y - p1.y) * (p2.x - p1.x) - (p2.y - p1.y) * (p3.x - p1.x) + +func polar_angle(reference, point: Point): float = + ## Find the polar angle of a point relative to a reference point + arctan2(float(point.y - reference.y), float(point.x - reference.x)) + +func flipped_point_cmp(pa, pb: Point): int = + ## Compare points first by their y-coordinate, then x-coordinate. + if (pa.y, pa.x) < (pb.y, pb.x): -1 + elif pa == pb: 0 + else: 1 + +func graham_scan(gift: seq[Point]): seq[Point] = + assert(gift.len >= 3) + var points = sorted(deduplicate(gift), flipped_point_cmp) + let pivot = points[0] + # Mimic sorting a sliced sequence without copying + sort(toOpenArray(points, 1, high(points)), + func (pa, pb: Point): int = + if polar_angle(pivot, pa) < polar_angle(pivot, pb): -1 + else: 1) + var + # Hull pointer + m = 1 + # Needed because the iteration variable from a slice is immutable + en = toSeq(low(points) + 2..high(points)) + for i in mitems(en): + while cross_product(points[m - 1], points[m], points[i]) <= 0: + if m > 1: + m -= 1 + # All points are collinear + elif i == points.len: + break + else: + i += 1 + # Counter-clockwise point found, update hull and swap + m += 1 + swap(points[i], points[m]) + points[0..m] + +when isMainModule: + let + test_gift = @[(-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)].map(t => tup_to_point(t)) + hull = graham_scan(test_gift) + echo "Initial gift:" + echo test_gift + echo "Final hull:" + echo hull diff --git a/contents/graham_scan/graham_scan.md b/contents/graham_scan/graham_scan.md index 586d209db..7830e0cd4 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="nim" %} +[import:11-16, lang:"nim"](code/nim/graham.nim) {% 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="nim" %} +[import:28-54, lang:"nim"](code/nim/graham.nim) {% 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="nim" %} +[import, lang:"nim"](code/nim/graham.nim) {% endmethod %} <script>