#lang racket

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Common Helper Procedures ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Procedure:
;;;   distance
;;; Parameters:
;;;   p1, p2. Two points
;;; Purpose:
;;;   calculate the distance between them
;;; Produces:
;;;   a real number
(define distance
  (lambda (pt1 pt2)
    (sqrt (+ (* (- (car pt1) (car pt2)) (- (car pt1) (car pt2)))
             (* (- (cdr pt1) (cdr pt2)) (- (cdr pt1) (cdr pt2)))))))

;;;;;;;;;;;;;
;;; Part 1 ;;
;;;;;;;;;;;;;

;;; Procedure:
;;;   closer
;;; Parameters:
;;;   p, a pair representing a cartesian coordinate
;;;   p1, a pair representing a cartesian coordinate
;;;   p2, a pair representing a cartesian coordinate
;;; Purpose:
;;;   Find which point, p1 or p2, is closest to p
;;; Produces:
;;;   closest, a pair representing a cartesian coordinate
(define closer
  (lambda (p p1 p2)
    (if (< (distance p p1) (distance p p2))
        p1
        p2)))

;;; Procedure:
;;;   get-closest-point
;;; Parameters:
;;;   p, a pair representing a cartesian coordinate
;;;   list, a list of coordinates (in the same format as p)
;;; Purpose:
;;;   Find the point in the list that is closest to p
;;; Produces:
;;;   closest, the point closest to p
;;; Pre-conditions:
;;;   list is non empty
(define get-closest-point
  (lambda (p list)
    (let kernel ([closest-so-far (car list)]
                 [remaining (cdr list)])
      (cond
        [(> (length remaining) 0)
         (kernel (closer p closest-so-far (car remaining)) (cdr remaining))]
        [else closest-so-far]))))

;;; Procedure:
;;;   closest-point-heuristic
;;; Parameters:
;;;   list, a list of cartesian coordinates (represented as pairs of numbers)
;;; Purpose:
;;;   Determine a solution to the traveling salesman problem, starting at a
;;;     random point in the list and continously going to the closest point
;;;     not yet visited until no such points remain.
;;; Produces:
;;;   route, a list of points representing the computed solution to the TSP
;;; Pre-conditions:
;;;   list is non empty
(define closest-point-heuristic
  (lambda (list)
    (let* ([local-list (shuffle list)]
           [first (car local-list)])
      (let kernel (
                   [seq (cons first '())]
                   [remaining (cdr local-list)])
        (cond
          [(null? remaining) (reverse seq)]
          [else (let ([next (get-closest-point (car seq) remaining)])
                  (kernel
                   (cons next seq)
                   (remove next remaining)))])))))


;;;;;;;;;;;;;
;;; Part 2 ;;
;;;;;;;;;;;;;

;;; Procedure:
;;;   earlier-sweep?
;;; Parameters:
;;;   p1, p2. Two points
;;; Purpose:
;;;   determines who is swept first when we do a diagonal sweep.
;;; Produces:
;;;   a boolean
(define earlier-sweep?
  (lambda (pt1 pt2)
    (< (+ (car pt1) (cdr pt2)) (+ (car pt2) (cdr pt2)))))

;;; Procedure:
;;;   diagonal-sweep-heuristic
;;; Parameters:
;;;   list-of-points, a list of points
;;; Purpose:
;;;   sort the list according to the order of
;;;     who is touched first during a diagonal sweep.
;;; Produces:
;;;   a list of sorted points.
(define diagonal-sweep-heuristic
  (lambda (list-of-points)
    (sort list-of-points earlier-sweep?)))


;;;;;;;;;;;;;
;;; Part 3 ;;
;;;;;;;;;;;;;

;;; Procedure:
;;;   random-route-heuristic
;;; Parameters:
;;;   list-of-points, a list of points
;;; Purpose:
;;;   make the input list random
;;; Produces:
;;;   a list of points.
(define random-route-heuristic
  (lambda (list-of-points)
    (shuffle list-of-points)))


;;;;;;;;;;;;;
;;; Part 4 ;;
;;;;;;;;;;;;;
;;; Procedure:
;;;   my-own-heuristic
;;; Parameters:
;;;   list-of-points, a list of points
;;; Purpose:
;;;   do a fan-shaped sweep and store the points
;;;     according to who is touched first
;;; Produces:
;;;   a list of points.
(define my-own-heuristic
  (lambda (list-of-points)
    (sort list-of-points
          (lambda (pt1 pt2)
            (if (< (/ (cdr pt1)
                      (if (= (car pt1) 0)
                          0.0001
                          (car pt1)))
                   (/ (cdr pt2)
                      (if (= (car pt2) 0)
                          0.0001
                          (car pt2))))
                #t
                #f)))))


;;;;;;;;;;;;;
;;; Part 5 ;;
;;;;;;;;;;;;;
;;; This function is provided by Professor Sam.
;;;
;;; Procedure:
;;;   random-point
;;; Parameters:
;;;   N/A
;;; Purpose:
;;;   Create a random point within the (100, 100) square.
;;; Produces:
;;;   a point.
(define random-point
  (lambda ()
    (cons (random 100) (random 100))))


;;; This function is provided by Professor Sam.
;;;
;;; Procedure:
;;;   random-points
;;; Parameters:
;;;   n, an integer
;;; Purpose:
;;;   Create a list of n random points within the (100, 100) square.
;;; Produces:
;;;   a list of points.
(define random-points
  (lambda (n)
    (if (zero? n)
        null
        (cons (random-point) (random-points (- n 1))))))

;;; Procedure:
;;;   total-distance
;;; Parameters:
;;;   list-of-output-points, a list of points
;;; Purpose:
;;;   calculate the total distance travelled from the first
;;;     point to the last point
;;; Produces:
;;;   a positive real number.
(define total-distance
  (lambda (list-of-output-points)
    (if (< (length list-of-output-points) 2)
        0
        (+ (distance (car list-of-output-points)
                     (car (cdr list-of-output-points)))
           (total-distance (cdr list-of-output-points))))))


;;;;;;;;;;;;;;
;;; Testing ;;
;;;;;;;;;;;;;;
(define Sample-1 (random-points 10))
(define Sample-2 (random-points 10))
(define Sample-3 (random-points 10))
(define Sample-4 (random-points 10))
(define Sample-5 (random-points 10))
(define Sample-6 (random-points 10))
(define Sample-7 (random-points 10))
(define Sample-8 (random-points 10))
(define Sample-9 (random-points 10))
(define Sample-10 (random-points 10))

(println "Sample-1")
(total-distance (closest-point-heuristic Sample-1))
(total-distance (diagonal-sweep-heuristic Sample-1))
(total-distance (random-route-heuristic Sample-1))
(total-distance (my-own-heuristic Sample-1))

(println "Sample-2")
(total-distance (closest-point-heuristic Sample-2))
(total-distance (diagonal-sweep-heuristic Sample-2))
(total-distance (random-route-heuristic Sample-2))
(total-distance (my-own-heuristic Sample-2))

(println "Sample-3")
(total-distance (closest-point-heuristic Sample-3))
(total-distance (diagonal-sweep-heuristic Sample-3))
(total-distance (random-route-heuristic Sample-3))
(total-distance (my-own-heuristic Sample-3))

(println "Sample-4")
(total-distance (closest-point-heuristic Sample-4))
(total-distance (diagonal-sweep-heuristic Sample-4))
(total-distance (random-route-heuristic Sample-4))
(total-distance (my-own-heuristic Sample-4))

(println "Sample-5")
(total-distance (closest-point-heuristic Sample-5))
(total-distance (diagonal-sweep-heuristic Sample-5))
(total-distance (random-route-heuristic Sample-5))
(total-distance (my-own-heuristic Sample-5))

(println "Sample-6")
(total-distance (closest-point-heuristic Sample-6))
(total-distance (diagonal-sweep-heuristic Sample-6))
(total-distance (random-route-heuristic Sample-6))
(total-distance (my-own-heuristic Sample-6))

(println "Sample-7")
(total-distance (closest-point-heuristic Sample-7))
(total-distance (diagonal-sweep-heuristic Sample-7))
(total-distance (random-route-heuristic Sample-7))
(total-distance (my-own-heuristic Sample-7))

(println "Sample-8")
(total-distance (closest-point-heuristic Sample-8))
(total-distance (diagonal-sweep-heuristic Sample-8))
(total-distance (random-route-heuristic Sample-8))
(total-distance (my-own-heuristic Sample-8))

(println "Sample-9")
(total-distance (closest-point-heuristic Sample-9))
(total-distance (diagonal-sweep-heuristic Sample-9))
(total-distance (random-route-heuristic Sample-9))
(total-distance (my-own-heuristic Sample-9))

(println "Sample-10")
(total-distance (closest-point-heuristic Sample-10))
(total-distance (diagonal-sweep-heuristic Sample-10))
(total-distance (random-route-heuristic Sample-10))
(total-distance (my-own-heuristic Sample-10))

(println "Edge case")
(total-distance '((cons 4 2)))
