繁体   English   中英

Python集合。计数器效率

[英]Python collections.Counter efficiency

我使用以下代码来实现一个函数,该函数在字符串s中查找字符串p的所有字符串。

class Solution(object):
    def findAnagrams(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: List[int]
        """
        ans = list()
        pcnt = collections.Counter(p)
        for i in range(len(s)):
            if collections.Counter(s[i:i+len(p)]) == pcnt:
                ans.append(i)
        return ans

当在大长度输入字符串s上运行时,它在在线代码测试系统中给出了“超出时间限制”的错误。 但是,以下代码将不会出现此类问题:

class Solution(object):
    def findAnagrams(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: List[int]
        """
        ls, lp = len(s), len(p)
        cp = collections.Counter(p)
        cs = collections.Counter()
        ans = []
        for i in range(ls):
            cs[s[i]] += 1
            if i >= lp:
                cs[s[i - lp]] -= 1
                if cs[s[i - lp]] == 0:
                    del cs[s[i - lp]]
            if cs == cp:
                ans.append(i - lp + 1)
        return ans

我能知道为什么吗? 似乎两种解决方案都使用两个最大尺寸为len(p)的计数器?

要了解为什么某些代码比其他代码运行得更快,您应该对其进行分析。 在Python中,开始分析的最简单方法是运行:

python -m cProfile <script.py>

在我的例子中,我写了一个简单的脚本,调用慢速解决方案或快速解决方案:

# Pasted code from original question.
# Also renamed the slow version to `SlowSolution` and the fast version to `FastSolution`.
...

# solution = FastSolution()
solution = SlowSolution()

print(solution.findAnagrams('abcdefg' + 'a' * 10000, 'gfedcba' + 'a' * 10000))

然后我使用SlowSolutionFastSolution运行脚本。 以下是使用SlowSolution输出的探查器结果:

$ python -m cProfile counter.py
[0]
         100204 function calls (100192 primitive calls) in 2.557 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10008    0.015    0.000    2.538    0.000 __init__.py:516(__init__)
    10008    0.009    0.000    2.522    0.000 __init__.py:585(update)
        7    0.000    0.000    0.000    0.000 _collections_abc.py:392(__subclasshook__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:16(__init__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:20(__enter__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:26(__exit__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:36(__init__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:52(_commit_removals)
        9    0.000    0.000    0.000    0.000 _weakrefset.py:58(__iter__)
    20022    0.007    0.000    0.007    0.000 _weakrefset.py:70(__contains__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:81(add)
    10008    0.010    0.000    0.017    0.000 abc.py:178(__instancecheck__)
      7/1    0.000    0.000    0.000    0.000 abc.py:194(__subclasscheck__)
        1    0.000    0.000    2.557    2.557 counter.py:1(<module>)
        1    0.000    0.000    0.000    0.000 counter.py:17(FastSolution)
        1    0.000    0.000    0.000    0.000 counter.py:3(SlowSolution)
        1    0.017    0.017    2.556    2.556 counter.py:4(findAnagrams)
    10008    2.490    0.000    2.490    0.000 {built-in method _collections._count_elements}
        2    0.000    0.000    0.000    0.000 {built-in method builtins.__build_class__}
        1    0.000    0.000    2.557    2.557 {built-in method builtins.exec}
        7    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
    10008    0.005    0.000    0.022    0.000 {built-in method builtins.isinstance}
      8/2    0.000    0.000    0.000    0.000 {built-in method builtins.issubclass}
    30024    0.003    0.000    0.003    0.000 {built-in method builtins.len}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        7    0.000    0.000    0.000    0.000 {method '__subclasses__' of 'type' objects}
       14    0.000    0.000    0.000    0.000 {method 'add' of 'set' objects}
        1    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        7    0.000    0.000    0.000    0.000 {method 'remove' of 'set' objects}

FastSolution

$ python -m cProfile counter.py
[0]
         146 function calls (134 primitive calls) in 0.005 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.001    0.000 __init__.py:516(__init__)
        7    0.000    0.000    0.000    0.000 __init__.py:536(__missing__)
        2    0.000    0.000    0.001    0.000 __init__.py:585(update)
        7    0.000    0.000    0.000    0.000 _collections_abc.py:392(__subclasshook__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:16(__init__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:20(__enter__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:26(__exit__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:36(__init__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:52(_commit_removals)
        9    0.000    0.000    0.000    0.000 _weakrefset.py:58(__iter__)
        8    0.000    0.000    0.000    0.000 _weakrefset.py:70(__contains__)
        7    0.000    0.000    0.000    0.000 _weakrefset.py:81(add)
        1    0.000    0.000    0.000    0.000 abc.py:178(__instancecheck__)
      7/1    0.000    0.000    0.000    0.000 abc.py:194(__subclasscheck__)
        1    0.000    0.000    0.005    0.005 counter.py:1(<module>)
        1    0.000    0.000    0.000    0.000 counter.py:17(FastSolution)
        1    0.004    0.004    0.005    0.005 counter.py:18(findAnagrams)
        1    0.000    0.000    0.000    0.000 counter.py:3(SlowSolution)
        1    0.001    0.001    0.001    0.001 {built-in method _collections._count_elements}
        2    0.000    0.000    0.000    0.000 {built-in method builtins.__build_class__}
        1    0.000    0.000    0.005    0.005 {built-in method builtins.exec}
        7    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.isinstance}
      8/2    0.000    0.000    0.000    0.000 {built-in method builtins.issubclass}
        6    0.000    0.000    0.000    0.000 {built-in method builtins.len}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        7    0.000    0.000    0.000    0.000 {method '__subclasses__' of 'type' objects}
       14    0.000    0.000    0.000    0.000 {method 'add' of 'set' objects}
        1    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        7    0.000    0.000    0.000    0.000 {method 'remove' of 'set' objects}

一开始读取的输出有点奇怪,但我们真的对tottime列很感兴趣。 这告诉我们在特定功能中花费了多少时间。

如您所见,该脚本几乎将所有时间都花在{built-in method _collections._count_elements} 这是一个Counter使用的内部方法,我们可以推断每次创建计数器时都会调用它(如collections.Counter(p) )。

为了使代码更快,您应该更少次地调用collections.Counter(...)和/或使用更短的字符串。 在慢速版本中,您计算len(p)字符len(s)次。 这有一个O(sp)的运行时间是二次的并解释为什么它对大输入这么慢。

另一方面,更快的解决方案只计算一次s每个字符,这给它一个O(s + p)的运行时间。 这速度更快,并且可以通过更大的输入进行扩展。

有关Python中的性能分析的更多信息,请参阅如何配置python脚本?

创建,计数和比较Counter对象的开销比列表要多。 这就是你所看到的精髓。 如果你想要更快的方法,你可以通过将p的排列构建为元组来完成anagram finder,然后根据元组检查s的切片。

class Solution(object):
    def findAnagrams(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: List[int]
        """
        ans = list()
        pcnt = collections.Counter(p)
        for i in range(len(s)):
            if collections.Counter(s[i:i+len(p)]) == pcnt:
                ans.append(i)
        return ans

    def findAnagrams2(self, s, p):
        ls, lp = len(s), len(p)
        cp = collections.Counter(p)
        cs = collections.Counter()
        ans = []
        for i in range(ls):
            cs[s[i]] += 1
            if i >= lp:
                cs[s[i - lp]] -= 1
                if cs[s[i - lp]] == 0:
                    del cs[s[i - lp]]
            if cs == cp:
                ans.append(i - lp + 1)
        return ans

    def findAnagrams3(self, s, p):
        p_all = tuple(''.join(x) for x in permutations(p,len(p)))
        return [i for i in range(len(s)) if s[i:i+len(p)] in p_all]

以下是在IPython中使用timeit的3种方法的简短比较:

In [33]: %%timeit
    ...: sol.findAnagrams('hello world he said eh', 'he')
    ...:
1000 loops, best of 3: 259 µs per loop

In [34]: %%timeit
    ...: sol.findAnagrams2('hello world he said eh', 'he')
    ...:
10000 loops, best of 3: 102 µs per loop

In [35]: %%timeit
    ...: sol.findAnagrams3('hello world he said eh', 'he')
    ...:
100000 loops, best of 3: 15.5 µs per loop

暂无
暂无

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

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