【问题标题】:Lisp macro (or function) for nested loops用于嵌套循环的 Lisp 宏(或函数)
【发布时间】:2012-04-27 03:27:08
【问题描述】:

是否可以编写一个 Common Lisp 宏,它接受一个维度和变量列表、一个(迭代的)主体,并创建由列表指定的尽可能多的嵌套循环组成的代码?

也就是说,类似于:

(nested-loops '(2 5 3) '(i j k) whatever_loop_body)

应该扩展为

(loop for i from 0 below 2 do
  (loop for j from 0 below 5 do
    (loop for k from 0 below 3 do
      whatever_loop_body)))

跟进

正如怀远正确指出的那样,我必须知道在编译时传递给宏的参数。如果你真的像我一样需要一个函数,看下面。

如果你对宏没问题,就去6502的递归解决方案,很棒。

【问题讨论】:

  • 您使用的是哪种 Lisp 方言? Common Lisp?

标签: recursion macros lisp nested-loops


【解决方案1】:

有时一种有用的方法是编写递归宏,即生成包含同一宏的另一个调用的代码的宏,除非情况简单到可以直接解决:

(defmacro nested-loops (max-values vars &rest body)
  (if vars
      `(loop for ,(first vars) from 0 to ,(first max-values) do
          (nested-loops ,(rest max-values) ,(rest vars) ,@body))
      `(progn ,@body)))

(nested-loops (2 3 4) (i j k)
  (print (list i j k)))

在上面,如果变量列表为空,则宏直接展开为主体形式,否则生成的代码是第一个变量上的 (loop...),在 do 部分包含另一个 (nested-loops ...) 调用。

宏在用于函数的正常意义上不是递归的(它不是直接调用自身),但宏扩展逻辑将为内部部分调用相同的宏,直到代码生成完成。

请注意,内部循环中使用的最大值形式将在外部循环的每次迭代中重新评估。如果表单确实是测试用例中的数字,则没有任何区别,但如果它们是例如函数调用,则不同。

【讨论】:

  • 这是我的目标,但没能做到。我正在研究你的代码。有没有什么秘籍让你学会了这种杂技的 Lisp 编码?
  • 似乎第一个索引错过了最后一次迭代,即在您的示例中,变量“i”只取值 0 和 1,缺少值 2。或者,作为更好的选择,变量“j”并且“k”应该从 0 变为它们的值减去 1:因此“计数”意味着每个变量的迭代次数。
  • 只是一个风格建议;当 vars 和 counts 为空时,尝试使用基本情况。然后宏观层面的逻辑都将首先出现,并且根据基本情况,将生成不同的代码。那么 dotimes 代码将只在非基本案例分支中,主体将只在基本案例分支中,并且您应该少一些反引号杂技要经过。 @mmj Let Over Lambda 在编写递归宏上有一个很好的章节。
  • @mmj:我不习惯loops,我没有意识到您正在寻找特定的构造。通常我从0 循环到(1- n)(因此n 迭代)。这两个问题都已修复,现在宏与您的问题文本匹配(说实话,我最初是在没有循环功能的 Lisp 方言 REPL 中实现的)。
  • @claytontstanley:这是我的第一个版本,但我不喜欢扩展为 progn 表单的想法。确实没有理由这样做,所以我回到了一个显然更容易理解的公式。感谢您指出这一点。
【解决方案2】:

您不需要引号,因为无论如何都需要在编译时知道维度和变量。

(defmacro nested-loops (dimensions variables &body body)
  (loop for range in (reverse dimensions)
        for index in (reverse variables)
        for x = body then (list y)
        for y = `(loop for ,index from 0 to ,range do ,@x)
        finally (return y)))

编辑:

如果在编译时无法确定尺寸,我们需要一个函数

(defun nested-map (fn dimensions)
  (labels ((gn (args dimensions)
             (if dimensions
               (loop for i from 0 to (car dimensions) do
                 (gn (cons i args) (cdr dimensions)))
               (apply fn (reverse args)))))
    (gn nil dimensions)))

并在调用时将主体包裹在 lambda 中。

CL-USER> (nested-map (lambda (&rest indexes) (print indexes)) '(2 3 4))

(0 0 0) 
(0 0 1) 
(0 0 2) 
(0 0 3) 
(0 0 4) 
(0 1 0) 
(0 1 1) 
(0 1 2) 
(0 1 3) 
(0 1 4) 
(0 2 0) 
(0 2 1) 
...

编辑(2012-04-16):

上述版本的nested-map 是为了更接近地反映原始问题陈述而编写的。正如 mmj 在 cmets 中所说,将索引范围从 0 到 n-1 可能更自然,如果我们不坚持迭代的行优先顺序,将反转移出内部循环应该会提高效率。此外,让输入函数接受元组而不是单个索引可能更明智,因为它与排名无关。这是一个带有上述更改的新版本:

(defun nested-map (fn dimensions)
  (labels ((gn (args dimensions)
             (if dimensions
               (loop for i below (car dimensions) do
                 (gn (cons i args) (cdr dimensions)))
               (funcall fn args))))
    (gn nil (reverse dimensions))))

那么,

CL-USER> (nested-map #'print '(2 3 4))

【讨论】:

  • 功能也很棒。我喜欢你的编码。由于我更喜欢​​从左侧开始滚动索引,因此我需要将 '(reverse args)' 替换为 'args',并在最后一行中将 'dimensions' 替换为 '(reverse dimensions)'。
  • 如果 n 是维度的值,我建议循环(在函数中)应该从 0 到 n-1(我的选择),或者从 1 到 n。在前一种情况下,您只需在循环子句中将 'to' 替换为 'below'。
【解决方案3】:

嗯。这是 common lisp 中此类宏的示例。但请注意,我不确定这实际上是一个好主意。但我们在这里都是成年人,不是吗?

(defmacro nested-loop (control &body body)
  (let ((variables ())
        (lower-bounds ())
        (upper-bounds ()))
    (loop
       :for ctl :in (reverse control)
       :do (destructuring-bind (variable bound1 &optional (bound2 nil got-bound2)) ctl
             (push variable variables)
             (push (if got-bound2 bound1 0) lower-bounds)
             (push (if got-bound2 bound2 bound1) upper-bounds)))
    (labels ((recurr (vars lowers uppers)
               (if (null vars)
                   `(progn ,@body)
                   `(loop 
                       :for ,(car vars) :upfrom ,(car lowers) :to ,(car uppers)
                       :do ,(recurr (cdr vars) (cdr lowers) (cdr uppers))))))
      (recurr variables lower-bounds upper-bounds))))

语法与您的建议略有不同。

(nested-loop ((i 0 10) (j 15) (k 15 20)) 
    (format t "~D ~D ~D~%" i j k))

扩展到

(loop :for i :upfrom 0 :to 10
  :do (loop :for j :upfrom 0 :to 15
            :do (loop :for k :upfrom 15 :to 20
                      :do (progn (format t "~d ~d ~d~%" i j k)))))

宏的第一个参数是列表形式的列表

(variable upper-bound)

(隐含下限为 0)或

(variable lower-bound upper-bounds)

多一点爱,一个人甚至可以拥有类似的东西

(nested-loop ((i :upfrom 10 :below 20) (j :downfrom 100 :to 1)) ...)

但是,如果loop 已经拥有所有这些功能,何必呢?

【讨论】:

  • 我选择其他答案是因为简洁,但我承认你的答案更灵活。我印象深刻。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-20
  • 2018-11-11
  • 1970-01-01
  • 2010-12-12
  • 1970-01-01
相关资源
最近更新 更多