所以我试图解决python中Euler项目的一些问题。我目前正在研究Problem 92方形数字链。基本上这个想法是,如果你取任何整数,并递归地对其组成数字求平方(例如42 = 4 2 + 2 2 = 20,然后2 2 + 0 2 = 4等),你总是以1或89结束。
我正在尝试编写一个程序,可以计算出1到10个 K 范围内的数字,最终会有89个,最后会有多少个数字结束于1.我不是在尝试存储哪些整数最终到达哪里,只有多少。目标是能够为最大的K可能做到这一点。 (这是Hackerrank对那些好奇的挑战)。
在我生命中为了大量的事情,我需要使用缓存。但是,这是缓存(最终占用大量RAM)和计算时间之间的平衡行为。
我的问题是我最终耗尽了内存。所以我试图限制我正在使用的缓存的长度。但是,我仍然内存不足。我似乎无法找到导致我内存不足的原因。
我在ubuntu 14.04 LTS上的pycharm上运行它。
我的问题:
有没有办法检查什么占用我的内存?是否有一些工具(或脚本)可以让我基本上监视程序中变量的内存使用情况?或者错误地假设如果我用完RAM,那一定是因为我程序中的某个变量太大了?我不得不承认我对程序中内存使用的精细细节并不是那么清楚....
编辑:当K = 8时,我用完了mem,所以对于10 8 的整数,这不是那么大。此外,我在10 8 之前进行了测试(因此10 7 ,终止但需要一些时间并且使用比较小计算更多的内存)。而且似乎没有限制我的缓存大小变量产生差异.....
答案 0 :(得分:3)
I would suggest testing various cache sizes to see if it is actually beneficial to have as large a cache as possible.
If you take any 10-digit number and compute the sum of squares of its digits, the sum will be at most 10*9*9 = 810. Thus, if you cache the result for numbers 1 to 810, then you should be able to process all numbers with between 4 and 10 digits without recursion.
In this way, I have processed the first 10^8 numbers in around 6 minutes with memory usage staying constant at roughly 10 MB.
答案 1 :(得分:2)
这是Mathias Rav的一个很好的想法,但保留了使用带有记忆的递归函数的想法。我们的想法是使用辅助函数来完成繁重的工作,并使主函数只进行迭代的第一步。第一步是将问题大小减小到一个缓存有用的大小。缓存仍然很小。我能够在大约10分钟内完成所有数字达到10 ** 8(由于递归导致的开销使得此解决方案的效率低于Mathias'解决方案):
cache = {}
def helper(n):
if n == 1 or n == 89:
return n
elif n in cache:
return cache[n]
else:
ss = sum(int(d)**2 for d in str(n))
v = helper(ss)
cache[n] = v
return v
def f(n):
ss = sum(int(d)**2 for d in str(n))
return helper(ss)
def freq89(n):
total = 0
for i in range(1,n+1):
if f(i) == 89: total += 1
return total/n
答案 2 :(得分:1)
这是Mathias Rav和John Coleman对答案的扩展评论。我打算把它作为社区维基的答案。约翰科尔曼说不要这样做,所以我不是。
我将从John Coleman的回答开始。
cache = {}
def helper(n):
if n == 1 or n == 89:
return n
elif n in cache:
return cache[n]
else:
ss = sum(int(d)**2 for d in str(n))
v = helper(ss)
cache[n] = v
return v
def f(n):
ss = sum(int(d)**2 for d in str(n))
return helper(ss)
通过将if
初始化为helper(n)
,一件可以加快速度的小事就是避免cache
中的{1:some_value, 89:some_other_value}
{1:1, 89:89}
。明显的初始化是{1:False, 89:True}
。不太明显但最终更快的初始化是if f(i) == 89: total += 1
。这样可以将if f(i): total += 1
更改为def helper(n):
l = []
while n not in cache :
l.append(n)
n = sum(int(d)**2 for d in str(n))
v = cache[n]
for k in l :
cache[k] = v
return v
。
另一件可能有用的小事就是摆脱递归。情况并非如此。为了摆脱递归,我们必须按照
的方式做一些事情f(n)
问题在于helper
遇到的几乎所有数字都已经在缓存中,这要归功于从f(n)
调用sum(int(d)**2 for d in str(n))
的方式。摆脱递归不必要地创建一个需要进行垃圾收集的空列表。
John Coleman的答案最大的问题是通过ss
计算数字的平方和。虽然非常pythonic,这是非常昂贵的。我首先将helper
和f
中的变量def ss(n):
return sum(int(d)**2 for d in str(n))
更改为函数:
def ss(n):
s = 0
while n != 0:
d = n % 10
n = n // 10
s += d**2
return s
仅此一点对性能无效。事实上,它会伤害性能。函数调用在python中很昂贵。通过使它成为一个函数,我们可以通过用整数运算替换字符串运算来做一些非pythonic的事情:
d*d
这里的加速非常重要;我的计算时间减少了30%。那不是很好。还有另一个问题,使用取幂运算符。几乎任何语言,但Fortran和Matlab,使用d**2
比cache = {1:False, 89:True}
def ss (n):
s = 0
while n != 0:
d = n % 10
n = n // 10
s += d*d
return s
def helper(n):
if n in cache:
return cache[n]
else:
v = helper(ss(n))
cache[n] = v
return v
def f(n):
return helper(ss(n))
def freq89(n):
total = 0
for i in range(1,n+1):
if f(i): total += 1
return total/n
print (freq89(int(1e7)))
快得多。这在python中肯定是这样的。这个简单的改变几乎将执行时间减少了30%。
将所有这些放在一起产生
N = int(1e7)
cache = {1:False, 89:True}
def ss(n):
s = 0
while n != 0:
d = n % 10
n //= 10
s += d*d
return s
def initialize_cache(maxsum):
for n in range(1,maxsum+1):
l = []
while n not in cache:
l.append(n)
n = ss(n)
v = cache[n]
for k in l:
cache[k] = v
def freq89(n):
total = 0
for i in range(1,n):
if cache[ss(i)]:
total += 1
return total/n
maxsum = 81*len(str(N-1))
initialize_cache(maxsum)
print (freq89(N))
我还没有利用Mathias Rav的答案。在这种情况下,摆脱递归是有意义的。它还有助于将循环嵌入到初始化缓存的函数内部的初始范围内(函数调用在python中很昂贵)。
ss(n)
上面大约需要16.5秒(在我的电脑上)来计算我的电脑上1(含)和10000(不包括)之间数字的比率。这几乎是初始版本(44.7秒)的三倍。上面计算1(包括)和1e8(不包括)之间的数字的比率需要花费三分多于3分钟。
事实证明我没有完成。当程序刚刚为12345678执行此操作时,无需计算(例如)12345679的数字的平方和。一个减少十分之九用例的计算时间的快捷方式得到回报。函数prevn = 0
prevd = 0
prevs = 0
def ss(n):
global prevn, prevd, prevs
d = n % 10
if (n == prevn+1) and (d == prevd+1):
s = prevs + 2*prevd + 1
prevs = s
prevn = n
prevd = d
return s
s = 0
prevn = n
prevd = d
while n != 0:
d = n % 10
n //= 10
s += d*d
prevs = s
return s
变得有点复杂:
setTimeout
有了这个,计算最多(但不包括)1e7的数字的比率需要6.6秒,最多但不包括1e8的数字为68秒。