更通用的lisp代码,用于生成对的组合

时间:2010-09-09 00:11:45

标签: lisp common-lisp cartesian

鉴于下面这件令人伤心的事情,它产生了所有只有两个范围的对 -

[53]> (setq thingie '())

NIL
[54]> (loop for i in (generate-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]>  

或者换句话说,这会产生一种二维离散布局。

我如何构建某种带有任意数量范围的对生成代码? (或生成n维离散布局)。

显然,一个解决方案是让defmacro采用列表列表并构建 n 循环来执行,但这并不是一种直截了当的方法。

4 个答案:

答案 0 :(得分:2)

(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) 

如果您更喜欢语法糖,

(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))

编辑:(简要说明)

gn,x的第一个参数是到目前为止构造的部分元组; y是剩余的元素袋。函数gn通过迭代其余一个包(car y)的每个元素i来扩展部分元组,以形成(cons i x)。当没有剩余的包(if语句的else分支)时,元组完成,所以我们在元组上调用提供的函数fn。

答案 1 :(得分:0)

对我来说显而易见的事情是递归函数。

答案 2 :(得分:0)

如果您将此视为控制结构,那么宏路径就是您的选择。如果你认为这是一种生成数据的方式,那么就可以使用递归函数了。

答案 3 :(得分:0)

您不需要显式递归(甚至是宏),这也可以通过高阶函数来完成:

(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))))

两个嵌套的内部高阶函数(mapcanmapcar)执行与示例中两个嵌套循环相同的函数。然后,外部高阶函数reduce首先将前两个范围的值组合成对,然后在其参数函数的每次调用之后,将某个过程再次应用于前一个调用的中间结果和下一个范围。