处理列表和词汇的惯用法

时间:2015-03-20 20:49:51

标签: python list dictionary python-3.4 pypy

最近我一直在发现处理列表,词组,集合等的“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)

此功能的工作原理如下:

  1. 维护一个素数列表和一个dict中相同素数的整数倍列表

  2. 这些倍数应大于或等于某个整数i

  3. 如果现有素数的整数倍数列表中不存在数字,那么它必须是素数并添加到素数列表中

  4. i(最小素数)开始增加2,最多x

  5. 返回素数列表

  6. 我已经使用列表,集合重写了这个函数几次,但这个版本似乎是最惯用的版本。它很简短,很容易阅读。

    如果有人愿意让我知道是否可以写得更清楚,请发表评论,因为我很乐意阅读。

    现在的问题是:这个函数的第一个版本不是很干净,而且更像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
    

1 个答案:

答案 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,所以你应该使用列表。