How to generate all the permutations of elements in a list one at a time in Lisp?

后端 未结 2 1528
广开言路
广开言路 2020-12-03 20:27

I already have the code to generate all the permutations for a list of elements. However, I realized that if I want to manipulate the lists that are generated, I would need

2条回答
  •  无人及你
    2020-12-03 20:40

    Here's a way (following the code structure by @coredump from their answer; runs about 4x faster on tio.run):

    (defun permutations (list callback)
      (when list
        (let* ((all (cons 'head (copy-list list)))           ; head sentinel FTW!
               (perm (make-array (length list))))
          (labels ((g (p i &aux (q (cdr p)))
                    (cond
                      ((null (cdr q))   
                         (setf (svref perm i) (car q))       ; the last item
                         (funcall callback perm))
                      (T (loop while q do 
                            (setf (svref perm i) (car q))    ; pick the item
                            (rplacd p (cdr q))               ; pluck it out
                            (g all (1+ i))                   ; recurse!
                            (rplacd p q)                     ; heal the list back
                            (pop p)  
                            (pop q))))))                     ; advance the pointers
            (g all 0))))) 
    
    ; > (permutations '(1 2 3) #'princ)
    ; #(1 2 3)#(1 3 2)#(2 1 3)#(2 3 1)#(3 1 2)#(3 2 1)
    

    This uses recursion to build an n nested loops structure for the n-long input list, at run time, with the fixed i = 0, 1, ..., n-1 in each nested loop being the position in the result-holding permutation array to put the picked item into. And when all the n positions in the array are filled, in the innermost loop (which isn't even a loop anymore as it has just one element left to process), the user-supplied callback is called with that perm array as an argument. The array is reused for each new permutation.

    Implements the "shrinking domains" paradigm as in this pseudocode:

    for item1 in list:
       domain2 = remove item1 from list by position
       for item2 in domain2:
          domain3 = remove item2 domain2 by position
          for item3 in domain3:
                 ......
                 ......
                 (callback (list item1 item2 ... item_n))
    

    but in the real code we do away with all the quadratic interim storage used by this pseudocode, completely, by surgically manipulating the list structure. About the only advantage of the linked lists is their O(1) node removal capability; we might as well use it!

    update: special-casing the last two elements of a permutation as well (by unrolling the last loop into the corresponding two calls to the callback) gives about ~ 1.5x additional speedup.

    (In case the TIO link ever rots, here's the pastebin with the working code.)

提交回复
热议问题