PasteRack.org
Paste # 87699
2019-02-16 19:09:27

Fork as a new paste.

Paste viewed 333 times.


Embed:

A Toy Quadtree Implementation

  1. #lang typed/racket
  2.  
  3. ; a naive hack at quadtrees by rob@robertlavery.com
  4. ; https://www.researchgate.net/profile/Raphael_Finkel/publication/220197855_Quad_Trees_A_Data_Structure_for_Retrieval_on_Composite_Keys/links/0c9605273bba2ece7b000000/Quad-Trees-A-Data-Structure-for-Retrieval-on-Composite-Keys.pdf
  5.  
  6.  
  7.  
  8. (provide node                          ; Type: A struct, recursive with Quadtree, representing a non-null record with four children.
  9.          Quadtree                      ; Type: Union type, either a null record or a node.
  10.          get-node-coords               ; Function: A null-gated function to provide the coordinates associated with a node.
  11.          get-node-child                ; Function: A null-gated function to provide a child under a node in a particular Direction.
  12.          Direction                     ; Type: A union type -- either a function that returns a child node, or the symbol 'self.
  13.          coordinate                    ; Type: A struct of X Y cartesian coordinates.
  14.          coordinate-x                  ; Function: Struct accessor for coordinates.
  15.          coordinate-y                  ; Function: Struct accessor for coordinates.
  16.          coordinate-compare            ; Function: Return a Direction when given two points.
  17.          quadtree-member?              ; Function: A predicate that returns #t iff given Quadtree contains given coordinate.
  18.          in-range?                     ; Function: A predicate that given x y z returns #t iff x <= z <= y.
  19.          neighbor?                     ; Function: A predicate that returns #t iff two coordinates are neighbors.
  20.          overlapping-quadrants         ; Function: Returns a list of Directions overlapping neighbor points to a given coordinate.
  21.          quadtree-neighbors            ; Function: Returns a list of coordinates from the Quadtree neighboring a given coordinate.
  22.          descendant-coordinates        ; Function: Return coordinates of a node and all its descendants.
  23.          quadtree-fold                 ; Function: Given a Quadtree and a list of coordinates, recursively insert coordinates and return resultant quadtree.
  24.          quadtree-insert               ; Function: Given a Quadtree and a coordinate, return a Quadtree with the new coordinate inserted.
  25.          quadtree-delete               ; Function: Given a Quadtree and a coordinate, return a Quadtree with the coordinate deleted. Nonrecursive wrapper for cps.
  26.          cape                          ; Type: A struct containing a Quadtree and a list of orphaned coordinates.
  27.          cps                           ; Function: Recursively delete a coordinate from a Quadtree, returning a cape containing the new Quadtree and a list of any orphaned coordinates.
  28.          print-all-coords)             ; Convenience function to print all the coordinates from a list of coordinates.
  29.  
  30.  
  31. (struct node ([ NE : Quadtree ]
  32.               [ NW : Quadtree ]
  33.               [ SW : Quadtree ]
  34.               [ SE : Quadtree ]
  35.               [ coords : coordinate]))
  36.  
  37. (define-type Quadtree (U Null node))
  38.  
  39. (struct coordinate ([x : Integer] [y : Integer]))
  40.  
  41. ; A null-checking wrapper for node-coords.
  42. (: get-node-coords (-> Quadtree coordinate))
  43. (define (get-node-coords quadtree)
  44.   (cond [(null? quadtree) (raise "get-node-coords cannot be called on a null Quadtree.")]
  45.         [else (node-coords quadtree)]))
  46.  
  47. ; A null-checking wrapper for node-<Direction>.
  48. (: get-node-child (-> Quadtree (-> node Quadtree) Quadtree))
  49. (define (get-node-child quadtree direction)
  50.   (cond [(null? quadtree) (raise "get-node-child cannot be called on a null Quadtree.")]
  51.         [else (direction quadtree)]))
  52.  
  53. (define-type Direction (U (-> node Quadtree) 'self))
  54.  
  55. (: in-range? (-> Integer Integer Integer Boolean))
  56. (define (in-range? x y z)(and (<= x z) (>= y z)))
  57.  
  58.  
  59. ; The comparison function discerns which quadrant below a node in which to categorize a set of coordinates.
  60. ; The Finkel and Bentley paper uses a convention by which, for those points lying on cardinal directions,
  61. ; Quadrants I and III are "open", while II and IV are "closed." It seems to me that this will inevitably
  62. ; result in an unbalanced tree, so my implementation gives each quadrant a cardinal direction.
  63. (: coordinate-compare (-> coordinate coordinate Direction))
  64. (define (coordinate-compare a b)
  65.   (cond [(and (equal? (coordinate-x a) (coordinate-x b))
  66.               (equal? (coordinate-y a) (coordinate-y b))) 'self]
  67.         [(and (<= (coordinate-x a) (coordinate-x b))
  68.               (<  (coordinate-y a) (coordinate-y b))) node-NE]
  69.         [(and (>  (coordinate-x a) (coordinate-x b))
  70.               (<= (coordinate-y a) (coordinate-y b))) node-NW]
  71.         [(and (>= (coordinate-x a) (coordinate-x b))
  72.               (>  (coordinate-y a) (coordinate-y b))) node-SW]
  73.         [(and (<  (coordinate-x a) (coordinate-x b))
  74.               (>= (coordinate-y a) (coordinate-y b))) node-SE]
  75.         ; If you somehow raise this error, I did not account for something.
  76.         ; I am confident the other options are exhaustive.
  77.         [else (raise "coordinate-compare should never return this!")]))
  78.  
  79. (: quadtree-member? (-> Quadtree coordinate Boolean))
  80. (define (quadtree-member? quadtree coords)
  81.   (cond [(null? quadtree) #f]
  82.         [else (let ([quadrant (coordinate-compare (node-coords quadtree) coords)])
  83.                 (cond [(equal? quadrant 'self) #t]
  84.                       [else (quadtree-member? (get-node-child quadtree quadrant) coords)]))]))
  85.  
  86. (: neighbor? (-> coordinate coordinate Boolean))
  87. (define (neighbor? c1 c2)
  88.   (let ([x1 : Integer (coordinate-x c1)]
  89.         [y1 : Integer (coordinate-y c1)]
  90.         [x2 : Integer (coordinate-x c2)]
  91.         [y2 : Integer (coordinate-y c2)])
  92.     (and
  93.      (and (in-range? (- x1 1) (+ x1 1) x2)
  94.           (in-range? (- y1 1) (+ y1 1) y2))
  95.      (nand (equal? x1 x2) (equal? y1 y2)))))
  96.  
  97. (: overlapping-quadrants (-> coordinate coordinate (Listof (-> node Quadtree))))
  98. (define (overlapping-quadrants c1 c2)
  99.   (let* ([x1 : Integer (coordinate-x c1)]
  100.          [y1 : Integer (coordinate-y c1)]
  101.          [x2 : Integer (coordinate-x c2)]
  102.          [y2 : Integer (coordinate-y c2)]
  103.          [NE (if (and (>= (+ x2 1) x1) (>  (+ y2 1) y1)) node-NE null)]
  104.          [NW (if (and (<  (- x2 1) x1) (>= (+ y2 1) y1)) node-NW null)]
  105.          [SW (if (and (<= (- x2 1) x1) (<  (- y2 1) y1)) node-SW null)]
  106.          [SE (if (and (>  (+ x2 1) x1) (<= (- y2 1) y1)) node-SE null)])
  107.     ((inst filter (U Null (-> node Quadtree)) (-> node Quadtree)) procedure? (list NE NW SW SE))))
  108.  
  109. ; For now I'll just address the case I'll be using in Game of Life, and look for direct neighbors.
  110. (: quadtree-neighbors (-> Quadtree coordinate (Listof coordinate)))
  111. (define (quadtree-neighbors quadtree coords)
  112.   (cond [(null? quadtree) null]
  113.         [else
  114.          (let ([quadrants (overlapping-quadrants (node-coords quadtree) coords)]
  115.                [am-neighbor : (Listof coordinate) (if (neighbor? (node-coords quadtree) coords) (list (node-coords quadtree)) null)])
  116.            ((inst append coordinate)
  117.             ((inst append-map coordinate (-> node Quadtree))
  118.              (λ ([direction : (-> node Quadtree)])
  119.                (quadtree-neighbors (direction quadtree) coords)) quadrants) am-neighbor))]))
  120.  
  121. (: descendant-coordinates (-> Quadtree (Listof coordinate)))
  122. (define (descendant-coordinates quadtree)
  123.   (cond [(null? quadtree) null]
  124.         [else (let ([coords (list (get-node-coords quadtree))])
  125.                 (append coords
  126.                         (append-map
  127.                          (λ ([direction : (-> node Quadtree)])
  128.                            (descendant-coordinates (direction quadtree)))
  129.                          (list node-NE node-NW node-SW node-SE))))]))
  130.  
  131. (: quadtree-fold (-> Quadtree (Listof coordinate) Quadtree))
  132. (define (quadtree-fold quadtree coord-list)
  133.   (cond [(null? coord-list) quadtree]
  134.         [else (quadtree-fold (quadtree-insert quadtree (first coord-list)) (rest coord-list))]))
  135.  
  136. (: quadtree-insert (-> Quadtree coordinate node))
  137. (define (quadtree-insert quadtree coords)
  138.   (cond [(null? quadtree) (node null null null null coords)]
  139.         [(equal? 'self (coordinate-compare (get-node-coords quadtree) coords)) quadtree]
  140.         [else
  141.          (let* ([quadrant (coordinate-compare (get-node-coords quadtree) coords)]
  142.                 [r (λ ([direction : (-> node Quadtree)])
  143.                      (if (equal? quadrant direction)
  144.                          (quadtree-insert (get-node-child quadtree direction) coords)
  145.                          (get-node-child quadtree direction)))])
  146.            (node
  147.             (r node-NE)
  148.             (r node-NW)
  149.             (r node-SW)
  150.             (r node-SE)
  151.             (get-node-coords quadtree)))]))
  152.  
  153.  
  154. ; Capes are constructs that let me hand up orphans as needed from the recursive part of the deletion mechanism.
  155. (struct cape ([quadtree : Quadtree]
  156.               [orphans : (Listof coordinate)]))
  157.  
  158. ; Deletion
  159. ; Given a null Quadtree, return it unchanged (it's already gone.)
  160. ; At the top of the recursion stack, take any remaining orphans and insert them into the new Quadtree.
  161. ; Quadtree-delete is a wrapper function whose job is to keep the orphans an internal matter.
  162. ; It calls the recursive cps function, which performs the deletion and hands up a 'cape' struct
  163. ; containing a list of orphaned nodes that need to be reinserted to the quadtree.
  164. (: quadtree-delete (-> Quadtree coordinate Quadtree))
  165. (define (quadtree-delete quadtree coords)
  166.   (let* ([crime-scene-and-orphans (cps quadtree coords)]
  167.          [crime-scene (cape-quadtree crime-scene-and-orphans)]
  168.          [orphans (cape-orphans crime-scene-and-orphans)])
  169.     (quadtree-fold crime-scene orphans)))
  170.  
  171. ; The best part is that after I implemented all this I realized I don't need deletion for Game of Life lol
  172. (: cps (-> Quadtree coordinate cape))
  173. (define (cps quadtree coords)
  174.   (cond [(null? quadtree) (cape null null)] ; Stop, he's already dead
  175.         [(equal? 'self (coordinate-compare (get-node-coords quadtree) coords)) ; Do the deed,
  176.          (cape null (rest (descendant-coordinates quadtree)))]                 ; but rescue the orphans.
  177.         [else
  178.          (let* ([quadrant (coordinate-compare (get-node-coords quadtree) coords)]
  179.                 [r (λ ([direction : (-> node Quadtree)]) (if (equal? quadrant direction)
  180.                                                              (cps (get-node-child quadtree direction) coords)
  181.                                                              (cape (get-node-child quadtree direction) null)))]
  182.                 [NE (r node-NE)] [NW (r node-NW)] [SW (r node-SW)] [SE (r node-SE)]
  183.                 [gather-orphans (λ ([cape-list : (Listof cape)]) (append-map cape-orphans cape-list))]
  184.                 [orphans (gather-orphans (list NE NW SW SE))])
  185.            (cape (node
  186.                   (cape-quadtree NE)
  187.                   (cape-quadtree NW)
  188.                   (cape-quadtree SW)
  189.                   (cape-quadtree SE)
  190.                   (get-node-coords quadtree)) orphans))]))
  191.  
  192. (: print-all-coords (-> (Listof coordinate) Void))
  193. (define (print-all-coords coord-list)
  194.   ((inst for-each coordinate)
  195.    (λ (coord) (fprintf (current-output-port)
  196.                        "X: ~s Y: ~s\n"
  197.                        (coordinate-x coord)
  198.                        (coordinate-y coord))) coord-list))
  199.  
  200. ; I started learning Racket about a week ago and the first thing I did was three separate implementations of Conway's Game of Life,
  201. ; each longer and less performant than the last. I decided that the problem was the data structures I was using, so I decided to implement
  202. ; my own toy Quadtree. Others have done it, but this one is mine, and I've had a lot of fun putting it together.

=>