开发者

More generic lisp code to generate combinations of pairs

开发者 https://www.devze.com 2023-01-15 03:50 出处:网络
Given this sad thing below, which generates all pairs of only two ranges - [53]> (setq thingie \'())

Given this sad thing below, which generates all pairs of only two ranges -

[53]> (setq thingie '())

NIL
[54]> (loop for i in (genera开发者_开发知识库te-range 0 3) do 
(loop for j in (generate-range 4 6) do 
(push (list i j) thingie)))

NIL
[55]> thingie

((3 6) (3 5) (3 4) (2 6) (2 5) (2 4) (1 6) (1 5) (1 4) (0 6) (0 5) (0 4))
[56]>  

Or, put another way, this generates sort of a two-dimensional discrete layout.

How would I go about building some sort of pairs-generating code that took arbitrary numbers of ranges? (Or generating an n-dimensional discrete layout).

Obviously one solution would be to have a defmacro that took a list-of-lists and built n loops for execution, but that doesn't feel a straightforward way to go.


(defun map-cartesian (fn bags)
  (labels ((gn (x y)
             (if y (mapc (lambda (i) (gn (cons i x) (cdr y))) (car y))
                 (funcall fn x))))
    (gn nil (reverse bags))))

CL-USER> (map-cartesian #'print '((1 2) (a b c) (x y)))

(1 A X) 
(2 A X) 
(1 B X) 
(2 B X) 
(1 C X) 
(2 C X) 
(1 A Y) 
(2 A Y) 
(1 B Y) 
(2 B Y) 
(1 C Y) 
(2 C Y) 

If you prefer syntax sugar,

(defmacro do-cartesian ((item bags) &body body)
  `(map-cartesian (lambda (,item) ,@body) ,bags))

CL-USER> (do-cartesian (x '((1 2) (a b c) (x y)))
           (print x))

Edit: (brief explanation)

The first parameter of gn, x, is the partial tuple constructed so far; y is the remaining bags of elements. The function gn extends the partial tuple by iterating over each element i of one of the remaining bags, (car y), to form (cons i x). When there's no remaining bags (the else branch of the if statement), the tuple is completed, so we invoke the supplied function fn on the tuple.


The obvious thing for me would be a recursive function.


If you're thinking of this as a control structure, the macro route is the way to go. If you're thinking of this as a way of generating data, a recursive function is the way to go.


You don't need explicit recursion (or even a macro), this can also be done with a higher-order function:

(defun tuples-from-ranges (range &rest ranges)
  (reduce (lambda (acc range)
            (mapcan (lambda (sublist)
                      (mapcar (lambda (elt)
                                (append sublist (list elt)))
                              (apply #'generate-range range)))
                    acc))
          ranges
          :initial-value (mapcar #'list (apply #'generate-range range))))

The two nested inner higher-order functions (mapcan and mapcar) perform the same function that the two nested loops in your example did. The outer higher-order function reduce will then first combine the values of the first two ranges to pairs, and after that in each invocation of its argument function apply the some process again to the intermediate results from the preceding invocation and the next range.

0

精彩评论

暂无评论...
验证码 换一张
取 消