我昨天开始使用Common Lisp进行编程。现在我要find the sum of all the multiples of 3 or 5 below 1000。我提出了:
(loop for n from 1 to 1000 when
(or
(eq (mod n 5) 0)
(eq (mod n 3) 0))
(sum n)))
我知道循环部分有效(loop for n from 1 to 1000 sum n)
来汇总前1000个数字。我知道((eq (mod n 5) 0) (mod n 3) 0))
部分有用。我知道(or (eq (mod n 5) 0) (eq (mod n 3) 0))
有效。所以它看起来像对我来说是一个强大的程序,但是当我运行它时,我得到错误:
1 =(SUM N)找到关键字在当前LOOP上下文之后获取LOOP子句的位置:WHEN(OR(EQ(MOD 1000 5)0) (EQ(MOD 1000 3)0)) 1#。 [SB-INT型的条件:SIMPLE-PROGRAM-ERROR]
我怀疑or-statement之后的(sum n)
出了问题。但我不知道为什么会这样或者我如何解决它。有人可以帮助我,让我的第一个Lisp程序工作吗?
答案 0 :(得分:6)
sum n
,而不是(sum n)
不要将sum n
括在括号中。 loop
宏是其自己的域特定语言,具有自己的语法。有了它,你(loop for ... sum n)
。语法在此生产中的loop上的HyperSpec条目中给出:
numeric-accumulation::= {count | counting | sum | summing | }
maximize | maximizing | minimize | minimizing {form | it}
[into simple-var] [type-spec]
如果听起来更好,你也可以写(loop for … summing n)
。这可能更像是一个自然的英语句子。
=
,eql
或zerop
,但不是eq
在HyperSpec中查找函数,宏等是一种很好的做法。正如Rainer Joswig指出的那样,你不应该使用eq
来比较数字。为什么?让我们在HyperSpec中查找它。例子包括:
(eq 3 3)
=> true
OR=> false
(eq 3 3.0) => false
(eq 3.0 3.0)
=> true
OR=> false
(eq #c(3 -4) #c(3 -4))
=> true
OR=> false
和 Notes 部分说明(强调添加):
打印时显示相同的对象不一定是eq 彼此。打印相同的符号通常是彼此相等的 因为使用了实习生功能。 然而,数字与 相同的值不一定是eq ,两个相似的列表通常不是 相同。
允许实现制作字符和“副本” 任何时候的数字。效果是Common Lisp不保证 即使当它的两个参数都是“相同的东西”时,该等式也是正确的 那个东西是一个字符或数字。
对于数字,您还需要其他东西。 =
是一个很好的通用数字比较,虽然它在这里比你需要的更多,因为它可以比较不同类型的数字。例如,(= 5 5.0)
是真的。由于您只关心0
,因此您可以使用zerop
,但这仍然会比您需要的工作多一些,因为它也会检查其他数字类型。例如,(zerop #c(0.0 0.0))
是真的。在这种情况下,由于(mod n …)
会给你一个整数,你可以使用eql
:
在下面的两个对象x和y中,eql的值为true 例:
- 如果x和y是eq。
- 如果x和y都是相同类型和相同值的数字。
- 如果它们都是代表相同角色的字符。
醇>
因此,您可以使用(or (eql (mod n 3) 0) (eql (mod n 5) 0))
。
现在,你的问题是关于一个特定的循环语法,并且有一些关于相等运算符的要点。但是,由于其他一些答案已经考虑了其他方法,我认为值得指出的是,很多更有效的方法来做到这一点。首先,让我们看一下总结给定限制下所有数字的倍数的方法。例如,对于数字3和包含限制26,我们有总和
?
= 3 + 6 + 9 + 12 + 15 + 18 + 21 + 24
=(3 + 24)+(6 + 21)+(9 + 18)+(12 + 15)
= 27 + 27 + 27 + 27
一般情况下,如果您尝试使用几个不同的数字,您可以计算出包含限制数l和数字n,您将添加数字对,如果存在奇数则可选择一半数字n的倍数小于l。我不打算完成整个推导,但你最终可以
(defun sum-of-multiples-below-inclusive (limit divisor)
(multiple-value-bind (quotient remainder)
(floor limit divisor)
(let ((pair (+ (- limit remainder) divisor)))
(multiple-value-bind (npairs half-pair)
(floor quotient 2)
(+ (* npairs pair)
(if (oddp half-pair)
(floor pair 2)
0))))))
然后,要找出小于给定数字的倍数的总和,您可以从限制中减去一个:
(defun sum-of-multiples-below (limit divisor)
(sum-of-multiples-below (1- limit) divisor))
然后,要扩展到你的情况,那里有多个除数,你需要添加一些这些数字,然后减去那些被计算两次的数字。例如,在您的情况下:
(+ (sum-of-multiples-below 1000 3)
(sum-of-multiples-below 1000 5)
(- (sum-of-multiples-below 1000 15)))
;=> 233168
(loop for i from 1 below 1000
when (or (eql 0 (mod i 3))
(eql 0 (mod i 5)))
sum i)
;=> 233168
现在,天真地使用time
可能导致误导性结果,但SBCL在评估表单之前编译表单,所以这并不太可怕。这是一个非常非常小的 micro -benchmark,但是请看一下每种形式中使用的周期数:
(time (+ (sum-of-multiples-below 1000 3)
(sum-of-multiples-below 1000 5)
(- (sum-of-multiples-below 1000 15))))
Evaluation took:
0.000 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
100.00% CPU
11,327 processor cycles
0 bytes consed
(time (loop for i from 1 below 1000
when (or (eql 0 (mod i 3))
(eql 0 (mod i 5)))
sum i))
Evaluation took:
0.000 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
100.00% CPU
183,843 processor cycles
0 bytes consed
使用已关闭的表单很多更快。如果我们使用更高的限制,则不同的更明显。我们来看看100,000:
(time (+ (sum-of-multiples-below 100000 3)
(sum-of-multiples-below 100000 5)
(- (sum-of-multiples-below 100000 15))))
Evaluation took:
0.000 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
100.00% CPU
13,378 processor cycles
0 bytes consed
(time (loop for i from 1 below 100000
when (or (eql 0 (mod i 3))
(eql 0 (mod i 5)))
sum i))
Evaluation took:
0.007 seconds of real time
0.004000 seconds of total run time (0.004000 user, 0.000000 system)
57.14% CPU
18,641,205 processor cycles
0 bytes consed
对于10,000,000,数字甚至更加惊人:
(time (+ (sum-of-multiples-below 10000000 3)
(sum-of-multiples-below 10000000 5)
(- (sum-of-multiples-below 10000000 15))))
Evaluation took:
0.000 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
100.00% CPU
13,797 processor cycles
0 bytes consed
(time (loop for i from 1 below 10000000
when (or (eql 0 (mod i 3))
(eql 0 (mod i 5)))
sum i))
Evaluation took:
0.712 seconds of real time
0.712044 seconds of total run time (0.712044 user, 0.000000 system)
100.00% CPU
1,916,513,379 processor cycles
0 bytes consed
其中一些Project Euler问题非常有趣。他们中的一些人有一些非常直接的天真解决方案,适用于小投入,但根本不能很好地扩展。
答案 1 :(得分:5)
我会像这样格式化代码:
(loop for n from 1 below 1000
when (or (zerop (mod n 3))
(zerop (mod n 5)))
sum n))
when
在行的开头or
LOOP
中的below
这种Loop宏可以追溯到70年代早期的Interlisp,早在Common Lisp存在之前。
答案 2 :(得分:1)
这是另一个没有loop
(defun sum-to-thousand (count result)
(cond ((> count 1000) result)
((= (mod count 3) 0) (sum-to-thousand (+ count 1) (+ count result)))
((= (mod count 5) 0) (sum-to-thousand (+ count 1) (+ count result)))
(t (sum-to-thousand (+ count 1) result))))
答案 3 :(得分:1)
我可以提出更多" lispier"变体:
CL-USER> (defun my-sum (&key (from 1) to dividers (sum 0))
(if (>= from to)
sum
(my-sum :from (1+ from)
:to to
:dividers dividers
:sum (if (some (lambda (x) (zerop (mod from x))) dividers)
(+ sum from)
sum))))
MY-SUM
CL-USER> (my-sum :to 1000 :dividers '(3 5))
233168