如何在Lisp中将所有可被3或5整除的数字加起来?

时间:2014-05-29 09:51:31

标签: lisp common-lisp sbcl

我昨天开始使用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程序工作吗?

4 个答案:

答案 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)。这可能更像是一个自然的英语句子。

=eqlzerop,但不是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   例:

     
      
  1. 如果x和y是eq。
  2.   
  3. 如果x和y都是相同类型和相同值的数字。
  4.   
  5. 如果它们都是代表相同角色的字符。
  6.   

因此,您可以使用(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