#-(and) "

P23 (**) Extract a given number of randomly selected elements from a list.
    The selected items shall be returned in a list.
    Example:
    * (rnd-select '(a b c d e f g h) 3)
    (E D A)

    Hint: Use the built-in random number generator and the result of problem P20.
"

;; The word "extract" and P20, seem to hint that a given element may
;; be choosen only once.  Must the order be random too?




;; Recursive solution using the function remove-at of P20:

(defun rnd-select (list count)
  (if (zerop count)
      '()
      (let ((i (random (length list)))) ; lenght and elt are O(n) ==> rnd-select is O(n²).
        (cons (elt list i) (rnd-select (remove-at list (1+ i)) (1- count))))))


;; Iterative solution using the function remove-at of P20:

(defun rnd-select (list count)
  (loop
     :repeat count
     :for len :from (length list) :by -1
     :for i = (random len)
     :collect (elt list i) ; elt is O(n) ==> rnd-select is O(n²).
     :do (setf list (remove-at list (1+ i)))))


;; This solution extract the items in the same order than in list, by
;; precomputing a random bitmap.  The result is O(n):

(defun rnd-select (list count)
  (let ((len (length list)))
    (cond
      ((zerop count) '())  ; none selected.
      ((= count len) list) ; all selected.
      ((< 0 count len)
       (let ((bits (make-array len :element-type 'bit :initial-element 0)))
         (loop
            :while (plusp count)
            :for i = (random len)
            :do (when (zerop (aref bits i))
                  (setf (aref bits i) 1)
                  (decf count)))
         (loop
            :for item :in list
            :for indicator :across bits
            :when (plusp indicator)
            :collect item)))
      (t (error "Invalid count, must be between 0 and ~A" len)))))


;; This other solution remembers the indices selected, and avoids
;; reusing them.  This gives a random order, but the time complexity
;; is not deterministically bound, only statistically bound (assuming
;; a correct pseudo-random  generator):

(defun rnd-select (list count)
  (let ((len (length list)))
    (cond
      ((zerop count) '())  ; none selected.
      ((<= 1 count len)
       (loop
          :with indices = '()
          :with result  = '()
          :while (plusp count)
          :for i = (random len)
          :unless (member i indices)
          :do (progn
                (push i indices)
                (push (elt list i) result) ; elt is O(n) ==> rnd-select is O(n²).
                (decf count))
          :finally (return result)))
      (t (error "Invalid count, must be between 0 and ~A" len)))))


;; Other algorithms for random arrangements can be found in TAOCP...

;;;; THE END ;;;;
ViewGit