鉴于下面这件令人伤心的事情,它产生了所有只有两个范围的对 -
[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 循环来执行,但这并不是一种直截了当的方法。
答案 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))))
两个嵌套的内部高阶函数(mapcan
和mapcar
)执行与示例中两个嵌套循环相同的函数。然后,外部高阶函数reduce
首先将前两个范围的值组合成对,然后在其参数函数的每次调用之后,将某个过程再次应用于前一个调用的中间结果和下一个范围。