如何循环具有任意数量变量的函数

问题描述 投票:0回答:2

我正在尝试找到一种方法来执行下面这些函数的一般功能,因此您可以放入具有任意多个输入的函数 f,并且程序应该为每个变量执行此嵌入式循环,并将给定的函数应用于每个循环。

(defun one (k1 f) 
    (funcall k1 (loop for x in domain collect (funcall f x))))

(defun two (k2 k1 f) 
    (funcall k2 (loop for y in domain collect
       (funcall k1 (loop for x in domain collect 
          (funcall f y x))))))

(defun three (k3 k2 k1 f) 
    (funcall k3 (loop for z in domain collect
       (funcall k2 (loop for y in domain collect
          (funcall k1 (loop for x in domain collect 
             (funcall f z y x))))))

我想做的是 Lisp 中的谓词逻辑,其中 kn 是 all och ex 量词,f 是一个包含 n 个自由变量的公式。 感谢您抽出宝贵时间!

loops lisp common-lisp
2个回答
2
投票

如果要迭代的维数在编译时已知,例如使用此语法,其中

(a b c)
表示存在三个级别的迭代:

(do-domains (a b c) domain
   ...)

...然后你可以写一个扩展成的宏:

(let ((var domain))
  (dolist (a var)
    (dolist (b var)
      (dolist (c var)
         ...)))

然而,在一般情况下,您可能希望接受变量的变量列表,在这种情况下,您可以编写递归函数。你正在用不同的变量迭代同一个域,所以我会按如下方式编写函数:

(defun iter-domain-helper (function domain dimension values)
  (if (>= dimension 1)
      (dolist (value domain)
        (iter-domain-helper function
                            domain
                            (1- dimension)
                            (list* value values)))
      (apply function values)))

iter-domain-helper
递归维度变量,它是一个整数,表示要生成的变量数。

在 0 级(或以下),它在给定的

apply
上调用
function
,迭代的当前值存储在
values
中。如果
values
的大小与
function
的签名不匹配,则会检测到错误。注意在递归调用中维度是如何减少的。

例如:

(let ((counter 0))
  (iter-domain-helper (lambda (a b c) 
                        (incf counter)
                        (print (list a b c)))
                      '(0 1 5 8 11)
                      3
                      nil)
  counter)

这会在标准输出上打印 5^3 = 125 个列表,每个列表的大小为 3.

你不需要让用户直接通过

values
函数,所以实际的解决方案应该更像这样:

(defun iter-domain (function domain dimension)
  (iter-domain-helper function domain dimension nil))

0
投票

我试着写这样一个宏,它允许可变数量的

k
s(N
k
s):

(defmacro %predcall (f domain &rest ks)
  (let* ((vars (loop for k in ks collect (gensym)))
         (expr `(funcall ,f ,@(reverse vars))))
    (loop for ki in ks
          for var in vars
          do (setf expr `(funcall ,ki (loop for ,var in ,domain collect ,expr)))
          finally (return expr))))

您可以通过以下方式进行测试:

(macroexpand-1 '(%predcall f domain k1))

;; (FUNCALL K1 (LOOP FOR #:G5665 IN DOMAIN COLLECT (FUNCALL F #:G5665))) ;
;; T

(macroexpand-1 '(%predcall f domain k1 k2))

;; (FUNCALL K2
;;  (LOOP FOR #:G5667 IN DOMAIN COLLECT
;;   (FUNCALL K1
;;    (LOOP FOR #:G5666 IN DOMAIN COLLECT (FUNCALL F #:G5667 #:G5666))))) ;
;; T

(macroexpand-1 '(%predcall f domain k1 k2 k3))

;; (FUNCALL K3
;;  (LOOP FOR #:G5670 IN DOMAIN COLLECT
;;   (FUNCALL K2
;;    (LOOP FOR #:G5669 IN DOMAIN COLLECT
;;     (FUNCALL K1
;;      (LOOP FOR #:G5668 IN DOMAIN COLLECT
;;       (FUNCALL F #:G5670 #:G5669 #:G5668))))))) ;
;; T

gensym
解决了以独特的方式命名可变数量的变量的问题 - 这样它们就不会相互干扰。

但是,这个辅助宏的调用中参数的顺序根本不是你在问题描述中带来的。

所以,我们可以使用另一个宏来准确获得您想要的语法/接口:

(defmacro predcall (&rest args)
  (let* ((args-without-last (butlast args))
         (ks (butlast args-without-last))
         (f  (car (last args-without-last)))
         (domain (car (last args))))
    `(%predcall f domain ,@(reverse ks))))

现在这个宏以参数的“更正”顺序触发辅助宏的调用:

(macroexpand-1 '(predcall k1 f domain))

;; (%PREDCALL F DOMAIN K1) ;
;; T


(macroexpand-1 '(predcall k2 k1 f domain))

;; (%PREDCALL F DOMAIN K1 K2) ;
;; T



(macroexpand-1 '(predcall k3 k2 k1 f domain))

;; (%PREDCALL F DOMAIN K1 K2 K3) ;
;; T

要测试这些调用是否会正确扩展为宏 - 你可以 编写另一个宏,然后调用内部宏调用的 macroexpand-1 调用。 我们调用这个宏来执行 macroexpand-1 调用:

(defmacro predcall-1 (&rest args)
  "macroexpand-1 over the called function"
  (let* ((args-without-last (butlast args))
         (ks (butlast args-without-last))
         (f  (car (last args-without-last)))
         (domain (car (last args))))
    `(macroexpand-1 '(%predcall f domain ,@(reverse ks)))))

(predcall-1 k1 f domain)

;; (FUNCALL K1 (LOOP FOR #:G5675 IN DOMAIN COLLECT (FUNCALL F #:G5675))) ;
;; T

(predcall-1 k2 k1 f domain)

;; (FUNCALL K2
;;  (LOOP FOR #:G5677 IN DOMAIN COLLECT
;;   (FUNCALL K1
;;    (LOOP FOR #:G5676 IN DOMAIN COLLECT (FUNCALL F #:G5677 #:G5676))))) ;
;; T

(predcall-1 k3 k2 k1 f domain)

;; (FUNCALL K3
;;  (LOOP FOR #:G5680 IN DOMAIN COLLECT
;;   (FUNCALL K2
;;    (LOOP FOR #:G5679 IN DOMAIN COLLECT
;;     (FUNCALL K1
;;      (LOOP FOR #:G5678 IN DOMAIN COLLECT
;;       (FUNCALL F #:G5680 #:G5679 #:G5678))))))) ;
;; T

所以你可以看到,你可以按照你想要的顺序输入参数。 (唯一的区别是

domain
作为最后一个参数输入 而您更喜欢将域捕获为闭包)。

所以最后,你可以用这种形式调用宏:

(predcall kn ... k3 k2 k1 f domain)

是不是很神奇,在 Common Lisp 中使用相对简洁的宏可以实现所有这些?

© www.soinside.com 2019 - 2024. All rights reserved.