Search code examples
listloopsrandomcommon-lispunique

Trying to return unique elements from list


I am really new to Lisp programming thus I would prefer figuring it on my own so any tips are appreciated! My goal is to retrieve a list of randomly picked unique items. The below code can sometimes return a list such as ("has a ball" "has a ball"). I would prefer it to check whether the members of the list are unique and if not discard one of the same items and add a random unique member.

   (defvar *pirateAttributes* 
    (list "has a pirate costume" "has a wooden leg" "has a 
    ball")
   )

   (defun select-attributes (n)
     (loop repeat n
        collect (nth (random (length *pirateAttributes*)) 
        *pirateAttributes*)
     ) 
   )

  (select-attributes 2)

UPDATE1: I made it work by googling a bunch and somehow found a way. I am sure there is a much better way. Open to your best answers since I have a working version! This time I kept the loop longer and decided to prune or use recursion to get the desired amount of attributes. It probably scales very poorly if there were many attributes. but here it goes...

(defun select-attributes (n)
   "returns n randomly picked attributes from *pirateAttributes* as a list"
  (setf mylist '())
    (loop repeat (+ n 10)
        do (push (nth (random (length *pirateAttributes*)) *pirateAttributes*)  mylist)
        (print mylist)
    )
    (setf mylist (remove-duplicates mylist) )
    (print mylist)
    (setf listlen (length mylist))
    (case n
        (1 (if(= listlen 1)(values)(setf mylist (first mylist))))
        (2 (if(= listlen 2)(values)(setf mylist (cdr mylist))))
        (3 (if(= listlen 3)(values)(select-attributes n)))
    )   
)

Solution

  • A reasonably simple solution would be to take the list of attributes and perform a Fisher-Yates shuffle on it.

    You can create a function that returns a new list containing the shuffled elements of the attribute list. The shuffle function below chooses a random element of the input list and conses it onto the result of shuffling the remaining elements.

    Then you can use the subseq function to take the number of elements you need from the shuffled list:

    (defvar *pirate-attributes* 
      (list "has a pirate costume"
            "has a wooden leg"
            "has a ball"
            "has a parrot"
            "has a monkey"
            "has a saber"
            "has an eye patch"
            "has a bottle of rum"))
    
    (defun shuffle (xs)
      (if (or (null xs)
              (null (cdr xs)))
          xs
          (let* ((i (random (length xs)))
                 (x (nth i xs)))
            (cons x (shuffle (append (subseq xs 0 i)
                                     (subseq xs (1+ i))))))))
    
    (defun select-random-n (xs n)
      (subseq (shuffle xs) 0 n))
    

    Sample REPL interaction:

    CL-USER> (select-random-n *pirate-attributes* 3)
    ("has a bottle of rum" "has a pirate costume" "has an eye patch")
    
    CL-USER> (select-random-n *pirate-attributes* 3)
    ("has an eye patch" "has a ball" "has a bottle of rum")
    
    CL-USER> (select-random-n *pirate-attributes* 2)
    ("has a monkey" "has a parrot")
    
    CL-USER> (select-random-n *pirate-attributes* 5)
    ("has a ball" "has a saber" "has an eye patch" "has a wooden leg"
     "has a parrot")
    
    CL-USER> (select-random-n *pirate-attributes* 5)
    ("has a wooden leg" "has a monkey" "has a ball" "has a pirate costume"
     "has a parrot")