在尝试解决Project Euler中的问题时,我编写了以下函数和宏:
(defun digits (n &key (base 10)) ;; Returns a list with the digits of 'n'
(if (< n base) (list n) ;; in a given base.
(multiple-value-bind (div rem)
(floor n base)
(concatenate 'list (digits div :base base) (list rem)))))
(defmacro test-palindromes (n1 n2)
(let* ((dn1 (digits n1)) (dn2 (digits n2))
(hash (loop for i in dn1 ; A-list describing digit associations
collecting (assoc i (pairlis dn2
(loop for i from 0 below (length dn1)
collecting i))))))
`(lambda (n1 n2) (and ,@(loop for i in hash
for j from 0
collecting `(char= (char n1 ,(cdr i)) (char n2 ,j)))))))
我想要做的是生成一个lambda,如果一个字符串对应另一个字符串的特定排列,则返回T
。例如:
1 > (pprint (macroexpand-1 '(test-palindromes 1089 9801)))
(LAMBDA (N1 N2)
(AND (CHAR= (CHAR N1 3) (CHAR N2 0))
(CHAR= (CHAR N1 2) (CHAR N2 1))
(CHAR= (CHAR N1 1) (CHAR N2 2))
(CHAR= (CHAR N1 0) (CHAR N2 3))))
1 >
当输入为整数时,它工作正常......
1 > (funcall (test-palindromes 1089 9801) "ALAS" "SALA")
T
1 > (funcall (test-palindromes 1089 9801) "ALAS" "SALE")
Nil
...但如果我尝试给它更复杂的输入,则会失败:
1 > (setf g 10)
10
1 > (funcall (test-palindromes 1089 (+ 9791 g)) "ALAS" "SALA")
> Error: The value (+ 9791 G) is not of the expected type REAL.
> While executing: CCL::<-2, in process listener(1).
> Type :POP to abort, :R for a list of available restarts.
> Type :? for other options.
2 >
在绝望中,通过将dn1
和dn2
设置为(eval (digits dn1))
和(eval (digits dn2))
,尝试了一种笨拙的解决方案。这产生了部分改善......
2 > (funcall (test-palindromes 1089 (+ 9791 g)) "ALAS" "SALA")
T
...但是这段代码仍然会失败:
(loop for pos from 0 to 66
nconcing (loop for i in (nthcdr (1+ pos) 4-digits)
for j from 0
when (equal (sort (digits (nth pos 4-digits)) #'<)
(sort (digits i) #'<))
collect (test-palindromes (nth pos 4-digits) i)))
> Error: Unbound variable: POS
> While executing: CCL::CHEAP-EVAL-IN-ENVIRONMENT, in process listener(1).
> Type :GO to continue, :POP to abort, :R for a list of available restarts.
> If continued: Retry getting the value of POS.
> Type :? for other options.
3 >
(变量4-digits
包含4位数字的所有正方形的有序列表。)
我想我应该跳过应该eval
编辑的内容,但我并不真正理解循环中发生了什么。为什么pos
不再被识别?这个循环可以工作吗?任何意见都表示赞赏。
谢谢, 圣保罗
答案 0 :(得分:5)
在REPL或文件中解释或编译代码时,将执行defmacro
正文中的代码。在test-palindromes
的最后一个循环中,您为宏提供了参数(nth pos 4-digits)
- 但pos
只有在loop
被执行时才有明智的含义
因此,您尝试做的事情无法通过宏完成,因为宏的输出取决于执行时输入的值。
但你可以使用一个函数:
* (defun test-palindromes (n1 n2)
(let* ((dn1 (digits (eval n1))) (dn2 (digits (eval n2)))
(hash (loop for i in dn1
collecting (assoc i (pairlis dn2
(loop for i from 0 below (length dn1)
collecting i))))))
(lambda (n1 n2)
(loop for i in hash
for j from 0
always (char= (char n1 (cdr i)) (char n2 j))))))
TEST-PALINDROMES
* (test-palindromes 1089 9801)
#<COMPILED-LEXICAL-CLOSURE (:INTERNAL TEST-PALINDROMES) #x21012A08CF>
* (funcall * "ALAS" "SALA")
T
如果您出于某种原因,以您在宏中的方式(即代替loop
)展开lambda的内容,请执行包含{{and
的{{1}}表单1}}比较),您也可以使用函数执行此操作:
char=
但可能这种做法没有用。
答案 1 :(得分:2)
你的宏只能使用文字数字参数,因为在编译时参数将是数字,但是你已经注意到宏不知道表达式将具有什么值,因此你需要将参数视为变成价值观的表达。您不能对表达式执行digits
,因为它可能是变量或类似(+ 3 4)
的表达式,并且宏将其作为数据获取。
宏仅用于语法转换。想象一下,我将完成这个功能:
(defun check-numbers (num1 num2)
(let ((same (= num1 num2))
(palinfrom (test-palindromes num1 num2)))
...))
Common Lisp可以在创建时编译此函数,因此它将扩展所有宏,并包含test-palindromes
,其中符号num1
和num2
作为参数。宏需要扩展为代码,通常会评估这两个以获取数字,然后它可以对该结果执行digits
。它不可能在编译时发生。它与此相同:
(cond ((test x) ...)
(t ...))
cond
是一个将其转换为if
的宏。它获得的值是代码作为结构。在此扩展时,test
和x
很可能不存在,因此宏只能使用它来生成此内容:
(if (test x)
...
...)
根据经验,如果它可以成为一个函数,你不应该做任何宏。