【问题标题】:Performance question about string slicing in Python关于 Python 中字符串切片的性能问题
【发布时间】:2020-03-04 15:42:17
【问题描述】:

我正在学习一些 Python,在此过程中,我正在从 codewars 中做一些简单的 katas。 我遇到了https://www.codewars.com/kata/scramblies 问题。

我的解决方案如下:

def scramble(s1,s2):
    result = True
    for character in s2:
        if character not in s1:
            return False
        i = s1.index(character)
        s1 = s1[0:i] + s1[i+1:]
    return result

虽然结果正确,但速度还不够快。我的解决方案在 12000 毫秒后超时。 我查看了其他人提出的解决方案,其中一个涉及制作一套。

  def scramble(s1,s2):
     for letter in set(s2):
        if s1.count(letter) < s2.count(letter):
          return False
     return True

为什么我的解决方案比其他解决方案慢很多?除非我误解了切片字符串的效率,否则它看起来不应该是这样。我解决这个问题的方法是否有缺陷?

【问题讨论】:

  • 设置速度很快。
  • 我用dis反汇编了这两个函数,你的是71个命令。而正确的只有
  • 字节码指令的数量对算法的效率影响不大,这里的集合除了迭代之外没有其他用途。迭代一个集合并不比迭代一个序列快;好处来自当集合小于序列时,因为有很多重复元素。

标签: python-3.x string set slice


【解决方案1】:

对于这种对程序运行时间有限制的在线编程挑战,测试输入将包含一些相当大的示例,并且通常设置时间限制,这样您就不必挤压每最后一毫秒的性能超出您的代码,但您必须编写一个足够低的算法computational complexity。要回答您的算法超时的原因,我们可以使用big O notation 对其进行分析以找出其计算复杂度。

首先,我们可以用复杂度标记每个单独的语句,其中ns1 的长度,ms2 的长度:

def scramble(s1,s2):
    result = True                  # O(1)
    for character in s2:           # loop runs O(m) times
        if character not in s1:        # O(n) to search characters in s1
            return False               # O(1)
        i = s1.index(character)        # O(n) to search characters in s1
        s1 = s1[0:i] + s1[i+1:]        # O(n) to build a new string
    return result                  # O(1)

那么总复杂度是 O(1 + m*(n + 1 + n + n) + 1) 或更简单的 O(m*n)。这对这个问题没有效率。


替代算法更快的关键在于set(s2) 仅包含字符串s2 中的不同字符。这很重要,因为构成这些字符串的字母表具有恒定的、有限的大小。小写字母大概是 26。鉴于此,替代算法的外循环实际上最多运行 26 次:

def scramble(s1,s2):
    for letter in set(s2):                      # O(m) to build a set 
                                                # loop runs O(1) times
        if s1.count(letter) < s2.count(letter):     # O(n) + O(m) to count
                                                    # chars from s1 and s2
            return False                            # O(1)
    return True                                 # O(1)

这意味着替代算法的复杂度为 O(m + 1*(n + m + 1) + 1) 或更简单的 O(m + n),这意味着它比您的算法渐进地更有效。

【讨论】:

  • 非常感谢您的回复。我知道大 O 表示法,但我不知道我的字符串操作是 O(n)。非常详细的答案,正是我想要的:)
  • 字符串操作是 O(n),因为字符串是不可变的,所以它们不能就地更改。在内存中构建一个新字符串需要分配和复制与字符串长度一样多的字符,在这种情况下为 O(n)。但是,请注意,由于您在循环中执行的其他两件事也是 O(n),因此更快的字符串操作不会降低算法的渐近复杂度。
【解决方案2】:

首先,set 速度很快,而且非常擅长它的工作。对于 in 之类的东西,setlist 快​​。

其次,您的解决方案比正确的解决方案做得方式。请注意,第二个解决方案永远不会修改 s1s2,而您的解决方案都采用 两片 s1 然后重新分配 s1。这与调用.index() 一起。切片并不是最快的操作,主要是因为必须分配内存和复制数据。 .remove() 可能会比 .index() 和你正在做的切片的组合更快。

这里的基本信息是,如果任务可以在更少的操作中完成,它显然会执行得更快。切片也比大多数其他方法更昂贵,因为分配空间和复制内存比正确解决方案使用的.count() 等计算方法更昂贵。

【讨论】:

  • 在替代解决方案中,该集合不用于in 或任何其他在集合上自然更快的操作。该集合的唯一用途是对其进行迭代。然而,对于这个特殊问题,只需要从字符串s2 中迭代 distinct 个字符,因此使用集合意味着每个不同的字符只检查一次,这比检查每个字符更有效多次出现。
  • 感谢您的回答。没想到切片这么贵
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 1970-01-01
  • 2011-11-09
  • 2014-07-21
  • 2012-02-15
相关资源
最近更新 更多