;;;  Additional ID3 functions for artificially adding noise to data and testing results on
;;;  pruned and unpruned decision-trees.

(defun id3-noise-test (examples train# feature-noise-level category-noise-level)  
;;; Run and test id3 on the examples after adding noise. Using train# training examples splitting the rest
;;; 50/50 for a pruning test set and a performance test set.  Feature and category noise levels should
;;; be between 0 and 1.  Shows original and pruned tree and their results on the test set.
  (setf examples (mix-up examples))  ; First randomize order of elements
  (let* ((training-examples (add-feature-and-category-noise (subseq examples 0 train#)
							    feature-noise-level category-noise-level))
	 (rest-examples  (subseq examples train#))
	 (num-prune-test (round (* 0.5 (length rest-examples))))
	 (prune-test-examples (add-feature-and-category-noise (subseq rest-examples 0 num-prune-test) 
							      feature-noise-level category-noise-level))
	 (testing-examples (add-feature-noise (subseq rest-examples num-prune-test) feature-noise-level))
	 (start-time (get-internal-run-time)))
    (format t "~%~%Training Set: ~A; Pruning Test Set: ~A; Performance Test Set: ~A"
	    train# num-prune-test (length testing-examples))
    (let* ((decision-tree (id3 training-examples)) pruned-tree)
      (format t "~%~%Run time: ~,2Fs" (seconds-since start-time))
      (format t "~%Decision tree: ~%~A" decision-tree)
      (test-examples testing-examples decision-tree)
      (setf pruned-tree (prune-decision-tree decision-tree prune-test-examples))
      (format t "~%~%Pruned Decision tree: ~%~A" pruned-tree)
      (test-examples testing-examples pruned-tree))))


(defun add-feature-and-category-noise (examples feature-noise-level category-noise-level)
;;; Add both feature and category noise to the examples at the given levels.
;;; That is, with the given probability replace a value with a random value
  (setf examples (copy-tree examples))
  (dolist (example examples examples)
    (if (>  category-noise-level (random 1.0))
	(setf (first example) (pick-one '(+ -))))
    (add-feature-noise-to-instance (second example) feature-noise-level)))

(defun add-feature-noise (examples probability)
;;; Add just feature noise to the examples
  (setf examples (copy-tree examples))
  (dolist (example examples examples)
    (add-feature-noise-to-instance (second example) probability)))

(defun add-feature-noise-to-instance (instance probability)
;;; Add feature noise to the instance by replacing each feature with random
;;; value from domain with the given probability
      (dotimes (i (length instance) instance)
	(if (>  probability (random 1.0))
	    (setf (nth i instance)
		  (pick-one (nth i *domains*))))))

(defun pick-one (list)
  "Pick an item randomly from the list"
  (nth (random (length list)) list))

(defun mix-up (list)
  "Randomize the order of elements in this list."
  (mapcar #'(lambda (pair) (rest pair))
	  (sort (mapcar #'(lambda (item) (cons (random 1.0) item)) list)
		#'(lambda (a b) (> (first a) (first b))))))
