在 Common Lisp 中计算向量的线性组合
Computing linear combination of vectors in Common Lisp
我正在使用 Common Lisp 进行一些数值计算,我需要计算具有给定数值系数的几个向量的线性组合。我正在重写一段 Fortran 代码,这可以通过 res = a1*vec1 + a2*vec2 + ... + an*vecn
来完成。我最初对 CL 的理解是每次都简单地写一些像这样的东西:
(map 'vector
(lambda (x1 x2 ... xn)
(+ (* x1 a1) (* x2 a2) ... (* xn an)))
vec1 vec2 ... vecn)
但我很快注意到这种模式会反复出现,因此开始编写一些代码将其抽象出来。因为向量的数量以及 lambda 参数的数量会因地而异,所以我认为需要一个宏。我想出了以下内容:
(defmacro vec-lin-com (coefficients vectors &key (type 'vector))
(let ((args (loop for v in vectors collect (gensym))))
`(map ',type
(lambda ,args
(+ ,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args)))
,@vectors)))
宏扩展表达式:
(vec-lin-com (10 100 1000) (#(1 2 3) #(4 5 6) #(7 8 9)))
产生看似正确的扩展:
(MAP 'VECTOR
(LAMBDA (#:G720 #:G721 #:G722)
(+ (* 10 #:G720) (* 100 #:G721) (* 1000 #:G722)))
#(1 2 3) #(4 5 6) #(7 8 9))
到目前为止,还不错...
现在,当我尝试在这样的函数中使用它时:
(defun vector-linear-combination (coefficients vectors &key (type 'vector))
(vec-lin-com coefficients vectors :type type))
我收到一个编译错误,主要说明 The value VECTORS is not of type LIST
。我不确定如何处理这个问题。我觉得我错过了一些明显的东西。任何帮助将不胜感激。
你掉进了文字陷阱。宏是语法重写,因此当您在语法列表中传递 3 个文字向量时,您可以在编译时迭代它们,但是用 bindnig 替换它到列表是不一样的。宏只能看到代码,它不知道 vectors
在执行它的操作时最终会在运行时绑定到什么。你也许应该把它变成一个函数:
(defun vec-lin-com (coefficients vectors &key (type 'vector))
(apply #'map
type
(lambda (&rest values)
(loop :for coefficient :in coefficients
:for value :in values
:sum (* coefficient value)))
vectors))
现在您的初始测试将无法运行,因为您通过了语法而不是列表。你需要引用文字:
(vec-lin-com '(10 100 1000) '(#(1 2 3) #(4 5 6) #(7 8 9)))
; ==> #(7410 8520 9630)
(defparameter *coefficients* '(10 100 1000))
(defparameter *test* '(#(1 2 3) #(4 5 6) #(7 8 9)))
(vec-lin-com *coefficients* *test*)
; ==> #(7410 8520 9630)
现在你可以把它变成一个宏,但是大部分工作都是通过扩展而不是宏来完成的,所以基本上你的宏会扩展成与我的函数正在做的类似的代码。
请记住,宏在编译时展开,因此表达式 ,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args)
在编译时必须有意义。在这种情况下,mapcar
为 coefficients
和 args
得到的所有内容都是源代码中的符号 coefficients
和 vectors
。
如果您希望能够使用一组未知参数(即编译时未知)调用 vec-lin-com
,您需要将其定义为一个函数。听起来您遇到的主要问题是正确排序 +
的参数。有一个 trick 使用 apply
和 map
来转置可能有帮助的矩阵。
(defun vec-lin-com (coefficients vectors)
(labels
((scale-vector (scalar vector)
(map 'vector #'(lambda (elt) (* scalar elt)) vector))
(add-vectors (vectors)
(apply #'map 'vector #'+ vectors)))
(let ((scaled-vectors (mapcar #'scale-vector coefficients vectors)))
(add-vectors scaled-vectors))))
这不是世界上最高效的代码;它做了很多不必要的事情。但它是有效的,如果您发现这是一个瓶颈,您可以编写更高效的版本,包括一些可以利用编译时常量的版本。
我正在使用 Common Lisp 进行一些数值计算,我需要计算具有给定数值系数的几个向量的线性组合。我正在重写一段 Fortran 代码,这可以通过 res = a1*vec1 + a2*vec2 + ... + an*vecn
来完成。我最初对 CL 的理解是每次都简单地写一些像这样的东西:
(map 'vector
(lambda (x1 x2 ... xn)
(+ (* x1 a1) (* x2 a2) ... (* xn an)))
vec1 vec2 ... vecn)
但我很快注意到这种模式会反复出现,因此开始编写一些代码将其抽象出来。因为向量的数量以及 lambda 参数的数量会因地而异,所以我认为需要一个宏。我想出了以下内容:
(defmacro vec-lin-com (coefficients vectors &key (type 'vector))
(let ((args (loop for v in vectors collect (gensym))))
`(map ',type
(lambda ,args
(+ ,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args)))
,@vectors)))
宏扩展表达式:
(vec-lin-com (10 100 1000) (#(1 2 3) #(4 5 6) #(7 8 9)))
产生看似正确的扩展:
(MAP 'VECTOR
(LAMBDA (#:G720 #:G721 #:G722)
(+ (* 10 #:G720) (* 100 #:G721) (* 1000 #:G722)))
#(1 2 3) #(4 5 6) #(7 8 9))
到目前为止,还不错... 现在,当我尝试在这样的函数中使用它时:
(defun vector-linear-combination (coefficients vectors &key (type 'vector))
(vec-lin-com coefficients vectors :type type))
我收到一个编译错误,主要说明 The value VECTORS is not of type LIST
。我不确定如何处理这个问题。我觉得我错过了一些明显的东西。任何帮助将不胜感激。
你掉进了文字陷阱。宏是语法重写,因此当您在语法列表中传递 3 个文字向量时,您可以在编译时迭代它们,但是用 bindnig 替换它到列表是不一样的。宏只能看到代码,它不知道 vectors
在执行它的操作时最终会在运行时绑定到什么。你也许应该把它变成一个函数:
(defun vec-lin-com (coefficients vectors &key (type 'vector))
(apply #'map
type
(lambda (&rest values)
(loop :for coefficient :in coefficients
:for value :in values
:sum (* coefficient value)))
vectors))
现在您的初始测试将无法运行,因为您通过了语法而不是列表。你需要引用文字:
(vec-lin-com '(10 100 1000) '(#(1 2 3) #(4 5 6) #(7 8 9)))
; ==> #(7410 8520 9630)
(defparameter *coefficients* '(10 100 1000))
(defparameter *test* '(#(1 2 3) #(4 5 6) #(7 8 9)))
(vec-lin-com *coefficients* *test*)
; ==> #(7410 8520 9630)
现在你可以把它变成一个宏,但是大部分工作都是通过扩展而不是宏来完成的,所以基本上你的宏会扩展成与我的函数正在做的类似的代码。
请记住,宏在编译时展开,因此表达式 ,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args)
在编译时必须有意义。在这种情况下,mapcar
为 coefficients
和 args
得到的所有内容都是源代码中的符号 coefficients
和 vectors
。
如果您希望能够使用一组未知参数(即编译时未知)调用 vec-lin-com
,您需要将其定义为一个函数。听起来您遇到的主要问题是正确排序 +
的参数。有一个 trick 使用 apply
和 map
来转置可能有帮助的矩阵。
(defun vec-lin-com (coefficients vectors)
(labels
((scale-vector (scalar vector)
(map 'vector #'(lambda (elt) (* scalar elt)) vector))
(add-vectors (vectors)
(apply #'map 'vector #'+ vectors)))
(let ((scaled-vectors (mapcar #'scale-vector coefficients vectors)))
(add-vectors scaled-vectors))))
这不是世界上最高效的代码;它做了很多不必要的事情。但它是有效的,如果您发现这是一个瓶颈,您可以编写更高效的版本,包括一些可以利用编译时常量的版本。