;
;circle.lisp
;

;algorithm to repeatedly remove k'th item 
;from circular list and determine which
;element is last to remain.

;algorithm is o(n) given input n, size of list

;note that algorithm is independent of k because
;(mod k n) is always used to determine which element
;of list to remove

(defun circle-nth(l k)

  "Compute index, from 0 to length of list, of k-th item in list l.
   Uses the trick that for any k, (mod k (length l))
   is the correct index which is equivalent to walking through the list
   circularly to get the k-th item -- saves steps for k > (length l)... "

  (mod k (length l))
 
)

(defun circle-remove(l n)

  "Remove (circle-nth n)-th item in list l"

  (let 
    (
     (k-th (circle-nth l n))
     )

    (format t "Remove #~d from ~s~%" k-th l)
  
    (cond
      ((null l) nil)				; can't remove from empty list
      ((zerop k-th) (butlast l))		; index 0 means remove last element
      (t 
       (append					; construct returned list from
	 (nthcdr k-th l)			; succeeding cdr and
	 (firstn (sub1 k-th) l) 		; list preceding removed element
	 )
      )
    )
  )
)

(defun circle-last(l k-th)

 "Remove k-th item in list l until only 1 element left"

 (format t "~%") 

 (if
   (cdr l)
   (circle-last (circle-remove l k-th) k-th)	; recurse 'til only one element left
   (car l)
   )
)

(defun circle-demo(n k)

  "Demonstrate circular removal algorithm for a given n, k"

  (cond
    ((null (integerp n)) (format t "~%N must be an integer!~%"))
    ((null (integerp k)) (format t "~%K must be an integer!~%"))
    ((lessp n 1)         (format t "~%N must be >= 1!~%"))
    ((lessp k 1)         (format t "~%K must be >= 1!~%"))
    ((greaterp n 26)     (format t "~%N must be <= 26!~%"))
    (t
     (prog (demlist last-one)
	   (setq demlist '(a b c d e f g h i j k l m n o p q r s t u v w x y z))
	   (setq demlist (firstn n demlist))
	   (format t "~%Remove every ~d~a" k
		   (case k
		     (1 "st")
		     (2 "nd")
		     (3 "rd")
		     (t "th")
		     )
		   )
	   (format t " element from ~s~%" demlist)
	   (setq last-one (circle-last demlist k))
	   (format t "~%Only ~a remains.~%" last-one)
	   (return last-one)
	   )
     )
    )
  )
