Как я могу сказать, повторяется ли строка в Python?
Я ищу способ проверить, повторяется ли данная строка для всей строки или нет.
примеры:
[
'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, и они кажутся хорошими, когда вы знаете, что ищете, или, по крайней мере, длину шаблона, который вы ищете. К сожалению, я не знаю ни.
Как я могу сказать, повторяется ли строка, и если да, то какова самая короткая повторяющаяся подпоследовательность?
13 ответов
вот краткое решение, которое позволяет избежать регулярных выражений и медленных циклов в Python:
def principal_period(s):
i = (s+s).find(s, 1, -1)
return None if i == -1 else s[:i]
посмотреть сообщество Wiki ответ начато @davidism для контрольных результатов. В резюме,
решение Дэвида Чжана-явный победитель, превосходящий всех остальных по крайней мере в 5 раз для большого набора примеров.
(это слова ответа, не мои.)
это основано на наблюдении, что строка периодическое тогда и только тогда, когда оно равно нетривиальному вращению самого себя. Спасибо @AleksiTorhamo за понимание того, что мы можем восстановить основной период из индекса первого появления s
на (s+s)[1:-1]
, и для информирования меня о необязательном start
и end
аргументы Python string.find
.
вот решение с использованием регулярных выражений.
import re
REPEATER = re.compile(r"(.+?)+$")
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.
регулярное выражение (.+?)+$
разделен на три части:
(.+?)
соответствующая группа, содержащая по крайней мере один (но как можно меньше) любого персонажа (потому что+?
is нежадный).+
проверяет наличие хотя бы одного повторения соответствующей группы в первой части.$
проверяет конец строки, чтобы убедиться, что нет дополнительного, неповторяющегося содержимого после повторных подстрок (и с помощьюre.match()
гарантирует, что нет неповторяющегося текста до повторяющиеся подстроки).
в Python 3.4 и позже вы можете бросить $
и использовать re.fullmatch()
вместо этого или (в любом Python, по крайней мере, до 2.3) идите в другую сторону и используйте re.search()
с регулярным выражением ^(.+?)+$
, все из которых более вниз к личному вкусу чем что-нибудь еще.
вы можете сделать замечание, что для того, чтобы строка считалась повторяющейся, ее длина должна быть кратна длине ее повторяющейся последовательности. Учитывая это, вот решение, которое генерирует делители длины от 1
to 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
EDIT: в Python 3,/
оператор изменился, чтобы сделать float деление по умолчанию. Чтобы получить int
разделение с Python 2, вы можете использовать оператора. Спасибо @TigerhawkT3 за доведение этого до моего сведения.
на //
оператор выполняет целочисленное деление как в Python 2, так и в Python 3, поэтому я обновил ответ для поддержки обеих версий. Часть, где мы проверяем, равны ли все подстроки, теперь является операцией короткого замыкания с использованием all
и генератор выражение.
обновление: в ответ на изменение исходного вопроса код теперь обновлен, чтобы вернуть наименьшую повторяющуюся подстроку, если она существует и None
если это не так. @godlygeek предложил использовать divmod
чтобы уменьшить количество итераций на divisors
генератор, и код был обновлен, чтобы соответствовать этому. Теперь он возвращает все положительные делители n
в порядке возрастания, исключая n
себя.
дальнейшее обновление для высокой производительности: после нескольких тестов я пришел к выводу, что простое тестирование на равенство строк имеет лучшую производительность из любого решения нарезки или итератора в Python. Таким образом, я взял лист из книги @TigerhawkT3 и обновил свое решение. Это теперь более 6x так же быстро, как и раньше, заметно быстрее, чем решение Tigerhawk, но медленнее, чем у Дэвида.
вот некоторые ориентиры для различных ответов на этот вопрос. Были некоторые удивительные результаты, включая дико различную производительность в зависимости от тестируемой строки.
некоторые функции были изменены для работы с Python 3 (в основном путем замены /
С //
для обеспечения целочисленного деления). Если вы видите что-то не так, хотите добавить свою функцию или хотите добавить другую тестовую строку, ping @ZeroPiraeus в Python чат.
вкратце: существует примерно 50-кратная разница между лучшими и худшими решениями для большого набора данных примера, предоставленных OP здесь (via этой комментарий). решение Дэвида Чжана является явным победителем, превосходя всех остальных примерно в 5 раз для большого набора примеров.
пару ответов очень медленно в очень больших случаях "без совпадения". В противном случае, функции кажется быть равен или явными победителями в зависимости от теста.
вот результаты, включая графики, сделанные с использованием matplotlib и seaborn, чтобы показать различные распределения:
корпус 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
корпус 2 (прилагаемые примеры-большие set)
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
корпус 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
тесты и сырые результаты доступны здесь.
решение без регулярных выражений:
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 для более длинных строк, и решение regex zero превосходит короткие строки. Он выходит на самый быстрый (согласно тесту давидизма на github-см. Его ответ) с строк около 1000-1500 символов. Несмотря на это, он надежно второй по скорости (или лучше) во всех случаях, которые я тестировал. Спасибо, ThatWeirdo.
во-первых, вдвое сократить строку, если это дубликат "2 части". Это уменьшает пространство поиска при четном числе повторов. Затем, работая вперед, чтобы найти наименьшую повторяющуюся строку, проверьте, приводит ли разделение полной строки на все более крупные подстроки только к пустым значениям. Только подстроки до length // 2
необходимо протестировать, так как все, что было бы без повторов.
def shortest_repeat(orig_value):
if not orig_value:
return None
value = orig_value
while True:
len_half = len(value) // 2
first_half = value[:len_half]
if first_half != value[len_half:]:
break
value = first_half
len_value = len(value)
split = value.split
for i in (i for i in range(1, len_value // 2) if len_value % i == 0):
if not any(split(value[:i])):
return value[:i]
return value if value != orig_value else None
Это возвращает кратчайшее совпадение или нет, если нет спичка.
проблема может быть решена в O(n)
в худшем случае с функцией префикс.
Примечание, это может быть медленнее в общем случае (UPD: и намного медленнее), чем другие решения, которые зависят от количества делителей n
, но обычно найти не удается раньше, я думаю, один из плохих случаев для них будет aaa....aab
, где находятся n - 1 = 2 * 3 * 5 * 7 ... *p_n - 1
a
' s
прежде всего вам нужно вычислить префикс функции
def prefix_function(s):
n = len(s)
pi = [0] * n
for i in xrange(1, n):
j = pi[i - 1]
while(j > 0 and s[i] != s[j]):
j = pi[j - 1]
if (s[i] == s[j]):
j += 1
pi[i] = j;
return pi
тогда либо нет ответа, либо самый короткий период
k = len(s) - prefix_function(s[-1])
и вы просто должны проверить, если k != n and n % k == 0
(если k != n and n % k == 0
тогда ответ s[:k]
, иначе нет ответа
вы можете проверить доказательство здесь (на русском языке, но онлайн-переводчик, вероятно, сделать трюк)
def riad(s):
n = len(s)
pi = [0] * n
for i in xrange(1, n):
j = pi[i - 1]
while(j > 0 and s[i] != s[j]):
j = pi[j - 1]
if (s[i] == s[j]):
j += 1
pi[i] = j;
k = n - pi[-1]
return s[:k] if (n != k and n % k == 0) else None
эта версия пытается только те длины последовательности кандидатов, которые являются факторами длины строки; и использует *
оператор для построения полнометражной строки из последовательности кандидатов:
def get_shortest_repeat(string):
length = len(string)
for i in range(1, length // 2 + 1):
if length % i: # skip non-factors early
continue
candidate = string[:i]
if string == candidate * (length // i):
return candidate
return None
благодаря TigerhawkT3 заметила, что length // 2
без + 1
не соответствует abab
случае.
вот прямое решение, без регулярных выражений.
для подстроки s
начиная с нулевого индекса, длин от 1 до len(s)
, проверьте, есть ли эта подстрока,substr
- это повторяющийся узор. Эта проверка может быть выполнена путем объединения substr
С ratio
раз, так что длина сформированной таким образом строки равна длине s
. Отсюда ratio=len(s)/len(substr)
.
вернуться, когда первая такая подстрока найдена. Это даст наименьшая возможная подстрока, если она существует.
def check_repeat(s):
for i in range(1, len(s)):
substr = s[:i]
ratio = len(s)/len(substr)
if substr * ratio == s:
print 'Repeating on "%s"' % substr
return
print 'Non repeating'
>>> check_repeat('254725472547')
Repeating on "2547"
>>> check_repeat('abcdeabcdeabcdeabcde')
Repeating on "abcde"
Я начал с более чем восьми решений этой проблемы. Некоторые из них были основаны на регулярных выражениях (match, findall, split), некоторые из нарезки строк и тестирования, а некоторые со строковыми методами (find, count, split). Каждый из них имел преимущества в ясности кода, размере кода, скорости и потреблении памяти. Я собирался опубликовать свой ответ здесь, когда заметил, что скорость выполнения была оценена как важная, поэтому я сделал больше тестов и улучшений, чтобы прийти к этому:
def repeating(s):
size = len(s)
incr = size % 2 + 1
for n in xrange(1, size//2+1, incr):
if size % n == 0:
if s[:n] * (size//n) == s:
return s[:n]
этот ответ кажется похожим на несколько другие ответы здесь, но у него есть несколько оптимизаций скорости, которые другие не использовали:
-
xrange
немного быстрее в этом приложении - если входная строка имеет нечетную длину, не проверяйте подстроки четной длины,
- С помощью
s[:n]
напрямую, мы избегаем создания переменной в каждом цикле.
мне было бы интересно посмотреть, как это работает в стандартных тестах с общим оборудованием. Я считаю, что это будет хорошо, если Дэвид Отличный алгоритм Чжана в большинстве тестов, но должен быть довольно быстрым в противном случае.
Я нашел эту проблему очень контр-интуитивной. Решения, которые я считал быстрыми, были медленными. Решения, которые выглядели медленными, были быстрыми! Кажется, что создание строки Python с помощью оператора multiply и сравнения строк сильно оптимизированы.
эта функция работает очень быстро (проверено, и это более чем в 3 раза быстрее, чем самое быстрое решение здесь на строках с более чем 100k символами, и разница становится больше, чем дольше повторяющийся шаблон). Он пытается свести к минимуму количество сравнений, необходимых для получения ответа:
def repeats(string):
n = len(string)
tried = set([])
best = None
nums = [i for i in xrange(2, int(n**0.5) + 1) if n % i == 0]
nums = [n/i for i in nums if n/i!=i] + list(reversed(nums)) + [1]
for s in nums:
if all(t%s for t in tried):
print 'Trying repeating string of length:', s
if string[:s]*(n/s)==string:
best = s
else:
tried.add(s)
if best:
return string[:best]
обратите внимание, что, например, для строки длины 8 он проверяет только фрагмент размера 4, и ему не нужно тестировать дальше, потому что шаблон длины 1 или 2 приведет к повторению шаблона длина 4:
>>> repeats('12345678')
Trying repeating string of length: 4
None
# for this one we need only 2 checks
>>> repeats('1234567812345678')
Trying repeating string of length: 8
Trying repeating string of length: 4
'12345678'
в ответе Дэвида Чжана, если у нас есть какой-то круговой буфер, это не сработает:principal_period('6210045662100456621004566210045662100456621')
из-за начала 621
, где бы мне хотелось его выплюнуть:00456621
.
расширяет свое решение, мы можем использовать следующее:
def principal_period(s):
for j in range(int(len(s)/2)):
idx = (s[j:]+s[j:]).find(s[j:], 1, -1)
if idx != -1:
# Make sure that the first substring is part of pattern
if s[:j] == s[j:][:idx][-j:]:
break
return None if idx == -1 else s[j:][:idx]
principal_period('6210045662100456621004566210045662100456621')
>>> '00456621'
вот код в python, который проверяет повторение подстроки в главной строке, заданной пользователем.
print "Enter a string...."
#mainstring = String given by user
mainstring=raw_input(">")
if(mainstring==''):
print "Invalid string"
exit()
#charlist = Character list of mainstring
charlist=list(mainstring)
strarr=''
print "Length of your string :",len(mainstring)
for i in range(0,len(mainstring)):
strarr=strarr+charlist[i]
splitlist=mainstring.split(strarr)
count = 0
for j in splitlist:
if j =='':
count+=1
if count == len(splitlist):
break
if count == len(splitlist):
if count == 2:
print "No repeating Sub-String found in string %r"%(mainstring)
else:
print "Sub-String %r repeats in string %r"%(strarr,mainstring)
else :
print "No repeating Sub-String found in string %r"%(mainstring)
вход:
0045662100456621004566210045662100456621
выход :
длина строки : 40
подстрока '00456621' повторяется в строке '0045662100456621004566210045662100456621'
вход :
004608294930875576036866359447
выход:
длина строки : 30
нет повторяющейся подстроки, найденной в строке '004608294930875576036866359447'