我正在寻找一种方法来测试给定的字符串本身的重复整个字符串或不。

示例︰

[
    '0045662100456621004566210045662100456621',             # '00456621'
    '0072992700729927007299270072992700729927',             # '00729927'
    '001443001443001443001443001443001443001443',           # '001443'
    '037037037037037037037037037037037037037037037',        # '037'
    '047619047619047619047619047619047619047619',           # '047619'
    '002457002457002457002457002457002457002457',           # '002457'
    '001221001221001221001221001221001221001221',           # '001221'
    '001230012300123001230012300123001230012300123',        # '00123'
    '0013947001394700139470013947001394700139470013947',    # '0013947'
    '001001001001001001001001001001001001001001001001001',  # '001'
    '001406469760900140646976090014064697609',              # '0014064697609'
]

都是字符串重复自己,这和

[
    '004608294930875576036866359447',
    '00469483568075117370892018779342723',
    '004739336492890995260663507109',
    '001508295625942684766214177978883861236802413273',
    '007518796992481203',
    '0071942446043165467625899280575539568345323741',
    '0434782608695652173913',
    '0344827586206896551724137931',
    '002481389578163771712158808933',
    '002932551319648093841642228739',
    '0035587188612099644128113879',
    '003484320557491289198606271777',
    '00115074798619102416570771',
]

就是那些不这样做。

我的字符串重复节可能很长,和本身的字符串可以是 500 或更多字符,所以循环尝试生成字符串的其余部分的图案然后检查模式与每个字符看上去简直太慢。乘以,大概数百个字符串,看不到任何直观的解决方案。

我已经有点了解到 regexes 和它们看起来是适用于当您知道的或者在要查找的内容模式所需的最小长度。遗憾的是,我知道两者都不。

如何可以断定如果一个字符串重复自己,如果是,什么是最短的重复子序列?

2015-04-06 23:02:09
问题评论:

遍历每个字符在尝试生成然后检查模式与字符串似乎awful 慢的其余部分的图案-但它是?

@TimCastelijns 不可否认,我没有实际测试的。

编写正则表达式来检测重复字符的可能的重复项

只匹配字符串,不完整的事物的一部分的 @AvinashRaj。

@AvinashRaj OP 询问所有可能的解决方案。链接到仅接受regex解决方案的问题。注意,正则表达式可能能够解决问题,但在多长时间更多,比实际所需。例如一个最佳的解决方案 (即线性时间) 可以使用后缀树的文本。您只需找到长重复的子字符串,并做一些检查,在长度上。

回答:

下面是一个简洁的解决方案,避免了正则表达式和慢 Python 中循环︰

def principal_period(s):
    i = (s+s).find(s, 1, -1)
    return None if i == -1 else s[:i]

请参阅社区 Wiki 应答启动的 @davidism 基准测试结果。总之,

David Zhang 解决方案是显而易见的优胜者,由至少 5 倍大的示例集 outperforming 其他所有。

(该应答词,不是我。)

这根据观测到一个字符串是周期性的当且仅当它等于不常用的自身旋转。感谢为实现我们再恢复主体段s中第一个匹配项的索引从 @AleksiTorhamo 到(s+s)[1:-1],并通知我的 Python 的string.find可选的startend参数.

您可以扩展此选项以使用.find().index()而不是in如找到最短重复的子序列。(s+s).find(s, 1, -1).

此外,我认为(s+s).find(s, 1, -1) (非常轻微) 速度比将(s+s)[1:-1].find(s),至少对于大字符串后的切片方法, 您需要创建另外一个拷贝 (几乎) 在整个字符串。

这就像如果您需要从周期性函数波形正弦或 cos,移位到右侧。因为它是完全周期性,波最终匹配完美...此解决方案为数学平行线是只非凡。:)我希望我可以给您 + ∞ upvotes。

这里有相关 Guido 的最近更新 PEP 8 :"保持一致在 return 语句中。函数中的所有 return 语句应返回的表达式,或者它们都不应该。如果任何 return 语句返回的表达式中,任何不返回任何值的返回语句应明确声明这为返回 None,并显式的返回语句应该出现在函数末尾(如果可到达)。"

@WayneConrad 采用字符串,说, "abcd",弹出关闭右侧的字符并把它回左边获得"dabc"此过程称为旋转向右 1 个字符的字符串重复nn个字符旋转向右一个字符串。现在发现我们有k个字符的字符串,是否向右旋转由k的任意多个离开未更改的字符串。一个不常用旋转的字符串是一个其字符数不多的字符串的长度。

下面是使用正则表达式的解决方案。

import re

REPEATER = re.compile(r"(.+?)1+$")

def repeated(s):
    match = REPEATER.match(s)
    return match.group(1) if match else None

循环访问中的问题的示例︰

examples = [
    '0045662100456621004566210045662100456621',
    '0072992700729927007299270072992700729927',
    '001443001443001443001443001443001443001443',
    '037037037037037037037037037037037037037037037',
    '047619047619047619047619047619047619047619',
    '002457002457002457002457002457002457002457',
    '001221001221001221001221001221001221001221',
    '001230012300123001230012300123001230012300123',
    '0013947001394700139470013947001394700139470013947',
    '001001001001001001001001001001001001001001001001001',
    '001406469760900140646976090014064697609',
    '004608294930875576036866359447',
    '00469483568075117370892018779342723',
    '004739336492890995260663507109',
    '001508295625942684766214177978883861236802413273',
    '007518796992481203',
    '0071942446043165467625899280575539568345323741',
    '0434782608695652173913',
    '0344827586206896551724137931',
    '002481389578163771712158808933',
    '002932551319648093841642228739',
    '0035587188612099644128113879',
    '003484320557491289198606271777',
    '00115074798619102416570771',
]

for e in examples:
    sub = repeated(e)
    if sub:
        print("%r: %r" % (e, sub))
    else:
        print("%r does not repeat." % e)

...产生以下输出︰

'0045662100456621004566210045662100456621': '00456621'
'0072992700729927007299270072992700729927': '00729927'
'001443001443001443001443001443001443001443': '001443'
'037037037037037037037037037037037037037037037': '037'
'047619047619047619047619047619047619047619': '047619'
'002457002457002457002457002457002457002457': '002457'
'001221001221001221001221001221001221001221': '001221'
'001230012300123001230012300123001230012300123': '00123'
'0013947001394700139470013947001394700139470013947': '0013947'
'001001001001001001001001001001001001001001001001001': '001'
'001406469760900140646976090014064697609': '0014064697609'
'004608294930875576036866359447' does not repeat.
'00469483568075117370892018779342723' does not repeat.
'004739336492890995260663507109' does not repeat.
'001508295625942684766214177978883861236802413273' does not repeat.
'007518796992481203' does not repeat.
'0071942446043165467625899280575539568345323741' does not repeat.
'0434782608695652173913' does not repeat.
'0344827586206896551724137931' does not repeat.
'002481389578163771712158808933' does not repeat.
'002932551319648093841642228739' does not repeat.
'0035587188612099644128113879' does not repeat.
'003484320557491289198606271777' does not repeat.
'00115074798619102416570771' does not repeat.

正则表达式(.+?)1+$分为三个部分︰

  1. (.+?)是一个包含至少一个 (尽可能少) 的任意字符的匹配组 (因为+?为非贪婪).

  2. 1+匹配组在第一部分中的至少一次重复检查。

  3. $检查字符串的末尾,以确保在重复的子字符串后没有额外的、 非重复的内容 (使用re.match()可确保没有任何非重复文本之前和重复的子字符串)。

在 Python 3.4 及更高版本,您无法删除$ re.fullmatch()相反,或 (在任何 Python 至少尽量回为 2.3) 转到另一种方法并使用使用 regex ^(.+?)1+$,所有这些都比其他任何个人品味到更多。

我有不知道为什么这简洁的两套并非高投票解决。其他答案不坏,但这就是好得多。(它可能使用频繁 denigrated 的正则表达式,但我可以进行检查,这远比其他更长的时间回答,很多嵌套的块,潜在的打字错误,关闭一个错误更容易,等等)。啊哦,更糟的是会更好我假设。

我认为有两个主要原因是︰ 1) 一些程序员像数学多他们喜欢 regexes,和超级长边的字符串 (它不会甚至出现在真实的数据) 2) 由于不同的长度和输入特性字符串的使不同答案提前拉性能,使该解决方案显示不理想。

@PaulDraper︰ 猜测什么正则表达式执行后面的场景吗?它在分析此字符串并将存储每个元素,直到发生 reapeatition,可进行匹配。这是相同什么 OP statet 作为速度太慢。我只是因为它是有 2 套并不是任何性能赢得。

@Zaibis,我通常会同意,但这是最短和最快的解决方案 (stackoverflow.com/a/29482936/1212596)...除了的 David 的已过帐后所做的评论。我实际上像 David 的方法的详细信息 (聪明 !)。

@jwg 边缘案例中基准而言 (其中我帮助开发 @davidism 第一次创建之后) 是人为的和非常不可能发生︰ 一万0s 跟1,例如 (这是图表上的远 outlier)。所有其他边缘情况下都没有像这样差的成绩事实上,尽管正在设计来攻克这个例子的算法。但是,David Zhang 答案是明显更好,这就是我开始将其提升其中原因。

您可以将观测到的字符串是被视为重复,其长度必须能被整除其重复序列的长度。假设这里有一个解决方案,从到的1生成长度的约数n / 2包容,将原始字符串分割成子字符串的长度约数,并测试是否相等的结果集:

from math import sqrt, floor

def divquot(n):
    if n > 1:
        yield 1, n
    swapped = []
    for d in range(2, int(floor(sqrt(n))) + 1):
        q, r = divmod(n, d)
        if r == 0:
            yield d, q
            swapped.append((q, d))
    while swapped:
        yield swapped.pop()

def repeats(s):
    n = len(s)
    for d, q in divquot(n):
        sl = s[0:d]
        if sl * q == s:
            return sl
    return None

编辑︰在 Python 3 /运算符已更改默认执行浮动部门。int除法从 Python 2,您可以使用//运算符相反。感谢您对 @TigerhawkT3 将这引起我的注意。

//运算符在 Python 2 和 Python 3 执行整数除法,因此我已经更新以支持两个版本的答案。我们测试以查看是否所有子字符串相等的部分现在是all和生成器表达式使用短路操作。

更新︰在响应中的原始问题的更改,该代码现在已更新如果不是返回的最小重复的子字符串,如果存在和None@godlygeek 建议使用divmoddivisors生成器中,在迭代次数减少了,在更新代码以匹配也。它现在将按升序排列,除n本身返回n的所有正约数。

高性能的进一步更新︰多个测试之后, 我已经得出的结论,只测试字符串相等在 Python 中有任何切片或迭代器解决方案的最佳性能。因此,我学完 @TigerhawkT3 的书从一片叶子并更新我的解决方案。现在是与之前有明显比 Tigerhawk 的解决方案更快但低于 David 的一样快了 6 倍。

此解决方案是令人惊叹。您可以更改要按照农产品消费模式的约数方法。这样,它产生的结果,找到。如果这不是最高的答案,会很可惜。其他的都蛮力。

它返回一个生成器对象创建从生成器表达式,它是隐式的制造者:) @JustinDanielson它将延迟-评估约数。

哦。我不知道的。嗯,甚至更好然后。: D我能理解为什么要避免 sqrt,但您可以使用 n/2 作为上面往除数范围。

@JustinDanielson 感谢您的建议,范围上限现(n/2)非独占。

n / 2divisors()n // 2?

以下是有关此问题的不同答案的某些准则。出现了一些令人惊讶的结果,包括大量不同的性能,具体取决于所测试的字符串。

某些函数已被修改为使用 Python 3 (主要通过替换为/ //以确保整除)。如果您看到错误,需要添加您的函数,或者要添加另一个测试字符串,ping @ZeroPiraeus 在Python chatroom.

总之︰ 关于 50 倍区别 OP 提供示例数据大集中的表现最好和最差的解决方案没有这里(通过注释)。David Zhang 解决方案是显而易见的优胜者,由大约 5 倍大的示例集 outperforming 所有其他人。

两个答案都非常缓慢非常大的"不匹配"的情况下。否则,函数似乎同样根据测试的不同匹配或清除的获奖者。

下面是结果,包括使用 matplotlib 和 seaborn 来显示不同的分配进行出图︰


Corpus 1 (提供示例-小组)

mean performance:
 0.0003  david_zhang
 0.0009  zero
 0.0013  antti
 0.0013  tigerhawk_2
 0.0015  carpetpython
 0.0029  tigerhawk_1
 0.0031  davidism
 0.0035  saksham
 0.0046  shashank
 0.0052  riad
 0.0056  piotr

median performance:
 0.0003  david_zhang
 0.0008  zero
 0.0013  antti
 0.0013  tigerhawk_2
 0.0014  carpetpython
 0.0027  tigerhawk_1
 0.0031  davidism
 0.0038  saksham
 0.0044  shashank
 0.0054  riad
 0.0058  piotr

Corpus 1 graph


Corpus 2 (提供示例-大型集)

mean performance:
 0.0006  david_zhang
 0.0036  tigerhawk_2
 0.0036  antti
 0.0037  zero
 0.0039  carpetpython
 0.0052  shashank
 0.0056  piotr
 0.0066  davidism
 0.0120  tigerhawk_1
 0.0177  riad
 0.0283  saksham

median performance:
 0.0004  david_zhang
 0.0018  zero
 0.0022  tigerhawk_2
 0.0022  antti
 0.0024  carpetpython
 0.0043  davidism
 0.0049  shashank
 0.0055  piotr
 0.0061  tigerhawk_1
 0.0077  riad
 0.0109  saksham

Corpus 1 graph


Corpus 3 (边缘案例)

mean performance:
 0.0123  shashank
 0.0375  david_zhang
 0.0376  piotr
 0.0394  carpetpython
 0.0479  antti
 0.0488  tigerhawk_2
 0.2269  tigerhawk_1
 0.2336  davidism
 0.7239  saksham
 3.6265  zero
 6.0111  riad

median performance:
 0.0107  tigerhawk_2
 0.0108  antti
 0.0109  carpetpython
 0.0135  david_zhang
 0.0137  tigerhawk_1
 0.0150  shashank
 0.0229  saksham
 0.0255  piotr
 0.0721  davidism
 0.1080  zero
 1.8539  riad

Corpus 3 graph


测试和原始的结果可在此处.

我阅读了您其他的答案,我想做很好的观察,可以将字符串拆分为两部分,第一次以避免计算 !:)我不确定如果我在一个测试中返回 aa 的解决方案故障。它将只无法满足它如果要求返回短重复的子字符串。两种方法,很好的准则,我非常喜欢。

@Shashank 基于在聊天中的一些讨论,op 如果可能想短重复。不期望任何人都知道这一点,但我必须要作为测试依据的内容.

对我这使它,以便它使用一个生成器,而不是列表理解的spl的代码进行了优化。这应避免大量的计算,在我看来会使有点更快 (虽然它可能仍不尽快为您的解决方案具有字符串重复除以 2,巧妙使用str.split绝妙的主意).

:-) 中的答案,但这就是再次链接 @GrijeshChauhan 的代码来创建图表它使用pandasmatplotlibseaborn.

@haccks seaborn 用于boxplot函数。使用pandas.DataFrame.plot(kind='boxplot')pandas.DataFrame.boxplot没有适当地显示离群值。另外,它使得事情 purdy。

非 regex 的解决方案︰

def repeat(string):
    for i in range(1, len(string)//2+1):
        if not len(string)%len(string[0:i]) and string[0:i]*(len(string)//len(string[0:i])) == string:
            return string[0:i]

更快的非正则表达式解决方案,这要归功于 @ThatWeirdo (请参阅注释)︰

def repeat(string):
    l = len(string)
    for i in range(1, len(string)//2+1):
        if l%i: continue
        s = string[0:i]
        if s*(l//i) == string:
            return s

上述解决方案比很少几个百分点,由原来但通常不错位更快-有时一大笔速度更快。未将快于 davidism 的长字符串,和零的正则表达式的解决方案更胜一筹的简短字符串。说到最快 (按照在 github-davidism 的测试见他答案) 与字符串共约 1000年-1500年个字符。无论如何,它是可靠的第二个最快 (或更好) 在所有情况下,我测试。谢谢,ThatWeirdo。

测试︰

print(repeat('009009009'))
print(repeat('254725472547'))
print(repeat('abcdeabcdeabcdeabcde'))
print(repeat('abcdefg'))
print(repeat('09099099909999'))
print(repeat('02589675192'))

结果︰

009
2547
abcde
None
None
None

这不是一个强力的解决方案吗?

@JustinDanielson 是。但解决方案 none 越小。

我能看到有关 1e-3e-5 为 4e 5 秒钟,让成功的 long 类型的值的短字符串的 3e 5 秒到 5 (1000年个字符) 的字符串,以及少量下成功长字符串 (最坏的情况) 以毫秒为单位。因此,关于第二个基本需要 1000 个 1000年个字符的字符串。这与数学答案,找到匹配 10 倍速度更快,但时间 3 次失败。

len(string[0:i])总是等于i(在这种情况下,至少)。更换这些,并且还在变量中保存len(string)string[0:i]可能会加快速度。此外 IMO 这;) 是一个很好的解决方案,超

@ThatWeirdo︰ 看到所做的编辑。您的建议使此解决方案的速度最快 (基于 davidism 的梗概在 github 上) 周围的文本字符串 1000年-1500年个字符,并且第二快的更短或更长的字符串。:)

请输入您的翻译

How can I tell if a string repeats itself in Python?

确认取消