[英]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
测试,反之亦然 。 但是,任何给定的数字都可能具有较小的除数而不是较大的除数; 所以我们应该按照升序进行测试。 并且,当素数p
是p*p > current
时我们可以停止 - 因为如果current == a*b
且a <= 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.