【问题标题】:Adversarial inputs for Python substring searchPython 子字符串搜索的对抗性输入
【发布时间】:2019-11-07 02:19:37
【问题描述】:

子字符串搜索的 CPython 实现(例如通过in)由以下算法实现。

def find(s, p):
    # find first occurrence of p in s
    n = len(s)
    m = len(p)
    skip = delta1(p)[p[m-1]]
    i = 0
    while i <= n-m:
        if s[i+m-1] == p[m-1]: # (boyer-moore)
            # potential match
            if s[i:i+m-1] == p[:m-1]:
                return i
            if s[i+m] not in p:
                i = i + m + 1 # (sunday)
            else:
                i = i + skip # (horspool)
        else:
            # skip
            if s[i+m] not in p:
                i = i + m + 1 # (sunday)
            else:
                i = i + 1
    return -1 # not found

至少,根据 CPython 实现的作者 (?) 所写的this source(取自this older answer)。

同一来源提到了该算法的最坏情况复杂度为O(nm),其中nm 是两个字符串的长度。我对这个界限是否紧密感兴趣。我的问题是:

是否有 Python in 中使用的算法的对抗性示例?我们能否给出一个字符串对序列(pattern, string),以便运行pattern in string 需要二次(或至少是超线性)时间?

演示朴素子字符串搜索的二次最坏情况运行时的标准示例,其中string = 'a'*npattern = 'a'*m + b does not work

【问题讨论】:

  • 您愿意考虑的算法的空间复杂度是多少?例如,有一个简单的 O(1) 时间子字符串搜索算法需要 O(n^2) 额外空间(前提是您显然没有计算构建数据结构的摊销成本)。
  • @Patrick87,我对考虑不同的字符串搜索算法不感兴趣。我对在提供的链接中描述的 CPython 中实现的特定一个感兴趣,以及它的最坏情况时间复杂度是多少。更具体地说,我有兴趣找到“实现”最坏情况时间复杂度的一系列字符串。
  • 我没有对此投票,但是我认为通过首先关注问题并减少文本,可以大大改善问题。例如。这个算法最差的时间复杂度是多少,什么字符串和子字符串会导致它?
  • @wihlke,谢谢你的建议,我已经更新了问题,以关注实际问题,而不是我问这个问题的细节。

标签: python string algorithm time-complexity complexity-theory


【解决方案1】:

s='a'*np='a'*m+'b' 的幼稚例子因为行不行

if s[i+m-1] == p[m-1]:

这将检查p ('b') 的最后一个字符(不是第一个字符)与s 中对应的当前位置。由于这失败了,那么结果就是对s 进行一次迭代,这就是它如此之快的原因。

如果你翻转ps='a'*np='b'+'a'*m),那么会发生类似的事情——这次上面的行通过了(p的最后一个字符现在是'a'),但是然后@987654333 @ 向前迭代,所以'b' 很快被找到,所以这个例子又是线性的和快速的。

将显示O(nm) 行为的简单示例更改为s='a'*np='a'*m+'ba'。在这种情况下,p 的最后一个字符是'a',因此初始检查通过,但是它需要遍历p 的其余部分才能到达'b'

# full='a'*n; sub='a'*m+'b'
>>> timeit("sub in full", "sub='a'*10+'b'; full='a'*100")
0.13620498299860628
>>> timeit("sub in full", "sub='a'*10+'b'; full='a'*1000")
0.9594046580004942
>>> timeit("sub in full", "sub='a'*100+'b'; full='a'*1000")
0.9768632190007338
# Linear in n, but m has minimal effect: ~O(n)

# full='a'*n; sub='a'*m+'ba'
>>> timeit("sub in full", "sub='a'*10+'ba'; full='a'*100")
0.35251976200015633
>>> timeit("sub in full", "sub='a'*10+'ba'; full='a'*1000")
3.4642483099996753
>>> timeit("sub in full", "sub='a'*100+'ba'; full='a'*1000")
27.152958754999418
# Both n and m have linear effect: ~O(nm)

【讨论】:

    【解决方案2】:

    试试这个:

    import re
    import time
    
    def slow_match(n):
        pat = 'a' + ('z' * n)
        str = 'z' * (n + n)
        start_time = time.time()
        if re.search(pat, str):
            print("Shouldn't happen")
        print(("Searched", n, time.time() - start_time))
    
    slow_match(10000)
    slow_match(50000)
    slow_match(100000)
    slow_match(300000)
    

    【讨论】:

    • 如果我运行它(包括更大的n,连续将其加倍至 9600000),它似乎在输入长度上花费线性时间,而不是二次(或其他可识别的超线性形式) ) 时间。使用in 而不是re.search(这是我的问题所在),结果也以线性时间给出,而且速度也快得多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-30
    • 2013-07-30
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    相关资源
    最近更新 更多