来自其他编程语言,我最近发现了 lisp 并感到惊讶。通过
loop
宏阅读,我想生成一个带有条件随机数的列表(让我们假设数字应该是偶数)。所以我想出了这个:
(defun some-test (range)
(loop repeat range
with item = (random 1000)
if (evenp item)
collect item))
但是,这给了我相同的随机数
range
次。我在这里做错了什么?显然,item
只被评估一次,而不是在每次迭代中都被评估。
with
在循环开始之前计算表达式。
如果要在每次迭代中计算中间值,则需要将其替换为for
:
(defun some-test (range)
(loop for item = (random 1000)
repeat range
if (evenp item)
collect item))
运行
(some-test 5)
然后返回最多5个偶数,例如:
CL-USER> (some-test 5)
(758 750 300)
CL-USER> (some-test 5)
(954)
作为奖励,这里有一种方法可以准确地收集满足列表中测试的 N 个值。这是作为一个高阶函数完成的,它接受一个
size
参数,以及两个函数,generate
和test
:
(defun generate-list (size generate test)
(when (> size 0)
(loop for val = (funcall generate)
if (funcall test val)
collect val
and count 1 into total
until (= total size))))
LOOP 标准中描述的有一些最初可能不明显的限制,即不能以任何顺序在其正文中混合各种子句,子句的不同部分必须按顺序出现.这在 LOOPS for Black Belts - Putting It All Together 中得到了很好的总结。通常,循环的终止测试必须出现在迭代子句之后,这使得在循环开始时测试零限制变得很麻烦。我选择用
when
将这种情况排除在循环之外,当条件不满足时无论如何都会返回NIL。
或者,我可以使用
initially
子句,以便所有内容都包含在循环体中:
(defun generate-list (size generate test)
(loop
:initially
(assert (>= size 0) (size) "Invalid size ~a" size)
(when (= size 0)
(return nil))
:for val = (funcall generate)
:if (funcall test val)
:collect val
:and :count 1 :into total
:until (= total size)))
例如:
(defun make-dice (limit)
(lambda ()
(random limit)))
(generate-list 10 (make-dice 1000) #'evenp)
=> (714 728 964 510 898 404 560 850 48 508)