最近我一直在发现处理列表,词组,集合等的“Python方式”。在这种程度上,我已经修改了我的函数来计算第一个N
质数,我们称之为' Pure Dict'版本:
def findprimeuptoG(x):
n_primes = {}
for i in range(2, x):
if i not in n_primes.values():
n_primes[i]=i
for k, v in n_primes.items():
if i > v:
n_primes[k] += k
return sorted(n_primes)
此功能的工作原理如下:
维护一个素数列表和一个dict中相同素数的整数倍列表
这些倍数应大于或等于某个整数i
如果现有素数的整数倍数列表中不存在数字,那么它必须是素数并添加到素数列表中
从i
(最小素数)开始增加2
,最多x
返回素数列表
我已经使用列表,集合重写了这个函数几次,但这个版本似乎是最惯用的版本。它很简短,很容易阅读。
如果有人愿意让我知道是否可以写得更清楚,请发表评论,因为我很乐意阅读。
现在的问题是:这个函数的第一个版本不是很干净,而且更像C:
def findprimeupto(x):
primes = []
n_primes = []
for i in range(2, x):
if i not in n_primes:
primes.append(i)
n_primes.append(i)
for j in range(len(primes)):
if i > n_primes[j]:
n_primes[j] += primes[j]
return primes
但是当我使用pypy
编译器运行它时,第一个版本绝对是最快的:
python3:
Primes up to: 100000
Algo: Clean version , primes: 9592, time: 102.74523687362671
Algo: Dict, Set , primes: 9592, time: 58.230621337890625
Algo: **FirstVersion** , primes: 9592, time: 59.945680379867554
Algo: List of List[1] , primes: 9592, time: 71.41077852249146
Algo: List of MutableNum , primes: 9592, time: 292.3777365684509
Algo: **Pure Dict** , primes: 9592, time: 56.381882667541504
pypy(ver 2.3.1):
Primes up to: 100000
Algo: Clean version , primes: 9592, time: 29.3849189281
Algo: Dict, Set , primes: 9592, time: 85.8557658195
Algo: **FirstVersion** , primes: 9592, time: 1.11557507515
Algo: List of List , primes: 9592, time: 42.2394959927
Algo: List of MutableNum , primes: 9592, time: 38.764893055
Algo: **Pure Dict** , primes: 9592, time: 110.416568995
我理解“Pure Dict”版本获得的性能是因为我没有在我的循环中使用迭代器,所以加速'FirstVersion'得到的是惊人的。
由于我们的大部分代码可能最终都会在生产中编译,我们是否应该以类似C的方式编写代码而不是惯用的Python?
编辑:
删除任何混淆我是否应该使用列表而不是dict我提交此功能的另一个版本,我称之为'清洁版'。这个版本不使用直接访问列表的第N个元素,而是以我认为最符合Python的方式迭代列表(顺便说一句,这个版本与同一代码的lisp版本最相似:)
def findprimeuptoB(x):
primes = []
n_primes = []
for i in range(2, x):
if not (i in n_primes):
primes.append(i)
n_primes.append(i)
new_n_primes = []
for prime, n_prime in zip(primes, n_primes):
if i > n_prime:
new_n_primes.append(prime + n_prime)
else:
new_n_primes.append(n_prime)
n_primes = new_n_primes
return primes
答案 0 :(得分:2)
是的,如果你关心表演,'第一版'是要走的路。您可以使用cProfile查看正在进行的操作。
供参考,在pypy 2.5.0上,使用' First Version'运行python -m cProfile -s cumulative x.py
给了我:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.727 0.727 x.py:1(<module>)
1 0.724 0.724 0.727 0.727 x.py:29(findprimeupto)
99999 0.002 0.000 0.002 0.000 {len}
99999 0.001 0.000 0.001 0.000 {range}
19184 0.001 0.000 0.001 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
OTOH,与纯粹的Dict&#39;,我得到:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 16.864 16.864 x.py:1(<module>)
1 1.441 1.441 16.864 16.864 x.py:1(findprimeuptoG)
99998 12.376 0.000 12.376 0.000 {method 'items' of 'dict' objects}
99998 3.047 0.000 3.047 0.000 {method 'values' of 'dict' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {len}
1 0.000 0.000 0.000 0.000 {range}
表明大部分时间都浪费在创建临时列表n_primes.items()
和n_primes.values()
上。
现在,可以轻松解决此问题:将.items()
和.values()
替换为各自的迭代器版本.iteritems()
和.itervalues()
。但是,结果仍然比列表版本慢得多,因为dicts的结构比列表更复杂,因此低级别的dict操作比等效的列表操作慢得多:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 3.155 3.155 x.py:1(<module>)
1 3.147 3.147 3.155 3.155 x.py:15(findprimeuptoH)
99998 0.006 0.000 0.006 0.000 {method 'itervalues' of 'dict' objects}
99998 0.002 0.000 0.002 0.000 {method 'iteritems' of 'dict' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {len}
1 0.000 0.000 0.000 0.000 {range}
最后,&#39;清洁版&#39;显然是相当糟糕的,因为它在每次迭代时都会创建一个新的n_primes
列表。实际上,我的时间是21.795秒。
结论:创建新容器(列表,字符串......)非常慢,尽可能避免使用它。此外,dicts比列表慢。在这个问题上,你实际上并不需要dicts,所以你应该使用列表。