繁体   English   中英

Lisp代码没有响应

[英]Lisp code is not responsive

剧透警报:这是Euler项目第7号的答案。

我正在学习Lisp而我正在使用compileonline.com来运行我的代码。 虽然在一个简单的程序上内存不足,所以我切换到桌面版本。 但是,即使这样也会耗尽内存。 有人能告诉我为什么这么糟糕吗?

在它当前的形式它不会挂起,但如果我将其更改为:

(if (= primecount 10001) 

它挂了。 即使是小到1000的东西也会挂起。

(setq current 3)
(setq primecount 1)
(setq primes (list 2))
(setq isprime "yes")

(loop for x from current do
  (list 
    (setq isprime "yes")
    (loop 
      for prime in primes do 
        (if (= (mod current prime) 0) 
          (list (setq isprime nil) (return)) 
          nil
        )
    )
    (if isprime 
      (list 
        (setq primecount (+ primecount 1)) 
        (setq primes (cons current primes)) 
        (if (= primecount 100) 
          (return) 
          nil
        )
      )
      (setq current (+ current 1))
    )
  )
)

(write-line (write-to-string primes))

你可以解释吗

  • 变量X是什么以及为什么它没有被使用?

  • LIST的呼吁是什么?

  • 你为什么要使用所有这些全局变量?

  • 你为什么不定义任何功能?

  • 为什么代码迭代所有素数?

这是重构和略微改进的版本,它基于您的代码:

(defun find-primes (n &aux (primes (list 2)) (l-primes primes) (n-primes 1))
  "returns a list of the first n primes"
  (flet ((prime-p (i &aux (limit (isqrt i)))
           "returns T if i is a prime number"
           (loop for prime in primes
                 until (> prime limit)
                 when (zerop (mod i prime))
                 do (return-from prime-p nil))
           t))
    (loop for i from 3
          until (>= n-primes n)
          do (when (prime-p i)
               (setf (cdr l-primes) (list i)
                     l-primes (cdr l-primes))
               (incf n-primes))))
  primes)

(print (find-primes 10000))

以上功能特点:

  • 它保留了素数列表,增加了
  • 它保留对素数列表的最后一个缺点的引用,以有效地添加到列表的末尾
  • 它有一个谓词子函数prime-p ,如果一个数字是素数,它返回T
  • 谓词使用函数isqrt来限制要搜索的数字

通过检查N和本地宏来推送到最后它看起来像这样:

(defun find-primes (n &aux (primes (list 2)) (l-primes primes) (n-primes 1))
  "returns a list of the first n primes"
  (check-type n (integer 1))
  (flet ((prime-p (i &aux (limit (isqrt i)))
           "returns T if i is a prime number"
           (loop for prime in primes
                 until (> prime limit)
                 when (zerop (mod i prime))
                 do (return-from prime-p nil))
           t))
    (macrolet ((push-last (obj place)
                 `(setf (cdr ,place) (list ,obj)
                        ,place (cdr ,place))))
      (loop for i from 3
            until (>= n-primes n)
            do (when (prime-p i)
                 (push-last i l-primes)
                 (incf n-primes)))))
  primes)

我们来试试吧:

CL-USER 3 > (time (find-primes 10000))
Timing the evaluation of (FIND-PRIMES 10000)

User time    =        0.052
System time  =        0.000
Elapsed time =        0.044
Allocation   = 292192 bytes

TL; DR :问题不在于记忆,而在于速度。

你在克服缺乏知识方面非常有创造力。 :)实际上,我们在Common Lisp中有一个progn ,它将几个表达式按顺序分组:

(defun myprimes-to (n &aux          ;; &aux vars may have init values
                      (current 3)
                      (primecount 1)
                      (isprime t) ; "yes"   ;; t is better 
                      (primes (list 2))
    (loop  ;for x from current do   ;; for a bare loop, just `loop` is enough
      (progn  ;list                 ;; `progn` groups expressions together to be
        (setq isprime t) ; "yes"    ;;    evaluated in order, one after another
        (loop                       ;;      (not even needed here, see comments below)
          for prime in primes do 
            (if (= (mod current prime) 0) 
                (progn  ;list
                  (setq isprime nil) 
                  (return))         ;; exit the loop
                nil))               ;; don't have to have alternative clause
        (if isprime 
            (progn  ;list 
              (incf primecount)  ; (setq primecount (+ primecount 1)) ;; same but shorter
              (setq primes (cons current primes)) 
              (if (= primecount n)  ;; abstract the n as function's argument
                  (return)          ;; exit the loop
                  nil))
            ;; What?? increment your loop var _only_ if it's not prime?
            (setq current (+ current 1)))))
    ;; (write-line (write-to-string primes))
    primes)                         ;; just return them

因此,如果current是素数,则重试。 由于它最后被添加到primes ,它现在将自己视为非素数,并且迭代将继续进行。 结果是正确的,但逻辑是乱码。

更重要的是,那里有两个主要的低效率, 算法 - 按顺序:你到目前为止所有的primes测试,反之亦然 但是,任何给定的数字都可能具有较小的除数而不是较大的除数; 所以我们应该按照升序进行测试。 并且,当素数pp*p > current时我们可以停止 - 因为如果current == a*ba <= b ,那么a*a <= b*a == current 早期停止会带来巨大的加速,它会将运行时复杂度从~n ^ 2改为大约~n ^ 1.5(在n个素数中产生)。


请注意这一点:您的代码在算法上比~n ^ 2 它将是~n ^ 2,如果它按素数按升序进行测试。 - 为什么这很重要? 它为你的问题提供了部分答案:你的问题不在于记忆,而在于速度 无论您的代码在n=100完成所需的时间,都需要超过100倍才能达到n=1000 (因为1000/100 == 10,而10 ^ 2 = 100)。 并且它将超过另外100倍,达到n=10000 因此,如果n=100问题花费了一秒的运行时间,则n=1000将花费100秒(~2分钟),并且n=10001分钟(超过3小时)。

我们总是可以根据经验估算出运行时的增长顺序; 看到这篇WP文章


因此,您需要按升序维护primes ,并在O(1)时间内append每个新素数append到它们的末尾。 你需要获得与代码行相同的结果(setq primes (append primes (list current)))才能实现; 但是你不能使用这个代码行,因为它在时间上是O(n),并且会大大减慢整个程序的速度。 (setq primes (reverse (cons current (reverse primes)))) 并且我们不能以相反的顺序维持primes因为我们必须按升序进行测试,并且简单地为每个新current调用(reverse primes)也是O(n)时间操作。

解决方案是使用rplacd :( (rplacd (last primes) (list current)) 即使这仍然是不对的,因为last也是O(n)操作。 相反,你必须维护一个额外的变量,指向质数列表的最后一个cons单元格,并在每次添加新的素数时将其前进: (setq last_cell (cdr last_cell))

当您运行固定代码时,您会发现它在大约0.1秒内完成了10,000个作业,并在该范围内以大约~n ^ 1.4个运行时的增长顺序运行

你的代码有些错误。 list函数用于构造和返回列表,它不应强制执行评估顺序。 您希望progn将一堆表单作为一个执行,并返回其最后一个表单的值。

我重写了一下:

(defvar current)
(setf current 3)
(defvar primecount)
(setf primecount 1)
(defvar primes)
(setf primes (list 2))
(defvar isprime)

(loop for x from current do
  (setq isprime t)
  (loop 
    for prime in primes do 
      (when (zerop (mod current prime))
        (setq isprime nil) (return)))
  (if isprime 
      (progn 
        (incf primecount)
        (push current primes)
        (when (= primecount 10001) 
          (return)))
      (incf current)))
(write-line (write-to-string primes))

我猜测你的代码使用大量内存,因为你使用列表作为你的数据结构,并且当它们变得庞大时,垃圾收集器会因创建和破坏列表而挣扎。

你的程序在我的SBCL for 10001中工作(包含在do-stuff函数中):

CL-USER> (time (do-stuff))
Evaluation took:
  13.486 seconds of real time
  13.438000 seconds of total run time (13.437000 user, 0.001000 system)
  99.64% CPU
  40,470,003,369 processor cycles
  1,674,208 bytes consed

CL-USER> (room)
Dynamic space usage is:   134,466,112 bytes.
Read-only space usage is:      5,856 bytes.
Static space usage is:         4,032 bytes.
Control stack usage is:        8,808 bytes.
Binding stack usage is:        1,056 bytes.
Control and binding stack usage is for the current thread only.
Garbage collection is currently enabled.

Breakdown for dynamic space:
  45,803,376 bytes for   569,191 instance objects.
  22,831,488 bytes for 1,426,968 cons objects.
  16,472,576 bytes for    16,135 code objects.
  15,746,176 bytes for   130,439 simple-vector objects.
  13,794,096 bytes for    36,381 simple-character-string objects.
  19,850,400 bytes for   440,297 other objects.
  134,498,112 bytes for 2,619,411 dynamic objects (space total.)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM