Я ищу способ проверить, повторяется ли данная строка для всей строки или нет.
Примеры:
[
'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 или более символов, поэтому циклически проходя по каждому символу, пытаясь построить шаблон, затем проверяя шаблон по сравнению с остальной строкой, кажется ужасно медленным. Умножьте это на потенциально сотни строк, и я не вижу никакого интуитивного решения.
Я немного изучил регулярные выражения, и они кажутся полезными, когда вы знаете, что ищете, или, по крайней мере, длину шаблона, который ищете. К сожалению, я не знаю ни того, ни другого.
Как я могу определить, является ли строка повторяющейся, и если это так, какова самая короткая повторяющаяся подпоследовательность?
Ответы:
Вот краткое решение, которое избегает регулярных выражений и медленных циклов в Python:
Посмотрите ответ сообщества Wiki, начатый @davidism для результатов теста. В итоге,
(Слова этого ответа, а не мои.)
Это основано на наблюдении, что строка является периодической тогда и только тогда, когда она равна нетривиальному вращению самой себя. Спасибо @AleksiTorhamo за понимание того, что мы можем затем восстановить основной период из индекса первого появления
s
in(s+s)[1:-1]
, и за информирование меня оstart
end
аргументах и аргументах Pythonstring.find
.источник
.find()
или.index()
вместоin
, например,.(s+s).find(s, 1, -1)
,(s+s).find(s, 1, -1)
это будет (очень немного) быстрее, чем(s+s)[1:-1].find(s)
, по крайней мере, для больших строк, так как нарезка означает, что вы должны создать еще одну копию (почти) всей строки."abcd"
из символа справа, и вставьте ее обратно влево, чтобы получить"dabc"
. Эта процедура называется вращением строки вправо на 1 символ . Повторитеn
время, чтобы повернуть строку вправо наn
символы. Теперь обратите внимание, что если у нас есть строкаk
символов, вращение вправо на любое кратное числоk
оставляет строку без изменений. Нетривиальным вращение струны один персонаж которого число не кратно длине струны.Вот решение с использованием регулярных выражений.
Перебирая примеры в вопросе:
... производит этот вывод:
Регулярное выражение
(.+?)\1+$
делится на три части:(.+?)
совпадающая группа, содержащая как минимум один (но как можно меньше) любого символа (потому что+?
он не жадный ).\1+
проверяет хотя бы одно повторение соответствующей группы в первой части.$
проверяет конец строки, чтобы убедиться, что после повторяющихся подстрок нет дополнительного неповторяющегося содержимого (а использованиеre.match()
гарантирует, что перед повторяющимися подстроками не будет неповторяющегося текста ).В Python 3.4 и более поздних версиях вы можете отказаться от
$
использования и использоватьre.fullmatch()
вместо него (или в любом Python, по крайней мере, начиная с 2.3) пойти другим путем и использоватьre.search()
с регулярным выражением^(.+?)\1+$
, все из которых более на свой вкус, чем что-либо еще.источник
Вы можете сделать замечание, что для строки, которая будет считаться повторяющейся, ее длина должна делиться на длину повторяющейся последовательности. Учитывая это, вот решение, которое генерирует делители длины от
1
доn / 2
включительно, делит исходную строку на подстроки с длиной делителей и проверяет равенство набора результатов:РЕДАКТИРОВАТЬ: В Python 3,
/
оператор изменился на деление с плавающей запятой по умолчанию. Чтобы получитьint
деление от Python 2, вы можете использовать//
вместо этого оператор. Спасибо @ TigerhawkT3 за то, что вы обратили на это мое внимание.В
//
выполняет оператор целочисленного деления в обоих Python 2 и Python 3, поэтому я обновил ответ на поддержку обеих версий. Часть, в которой мы проверяем, равны ли все подстроки, теперь представляет собой операцию короткого замыкания с использованиемall
выражения генератора.ОБНОВЛЕНИЕ: В ответ на изменение исходного вопроса, код был обновлен и теперь возвращает наименьшую повторяющуюся подстроку, если она существует, а
None
если нет. @godlygeek предложил использовать,divmod
чтобы уменьшить количество итераций вdivisors
генераторе, и код был обновлен, чтобы соответствовать этому. Теперь он возвращает все положительные делителиn
в возрастающем порядке, исключаяn
себя.Дальнейшее обновление для высокой производительности: после нескольких тестов я пришел к выводу, что простое тестирование на равенство строк дает лучшую производительность из всех решений срезов или итераторов в Python. Таким образом, я взял лист из книги @ TigerhawkT3 и обновил свое решение. Теперь он в 6 раз быстрее, чем раньше, заметно быстрее, чем решение Tigerhawk, но медленнее, чем решение Дэвида.
источник
(n/2)
включена.n / 2
вdivisors()
бытьn // 2
?Вот некоторые критерии для различных ответов на этот вопрос. Были неожиданные результаты, в том числе сильно отличающаяся производительность в зависимости от тестируемой строки.
Некоторые функции были изменены для работы с Python 3 (в основном путем замены
/
на//
для обеспечения целочисленного деления). Если вы видите что-то не так, хотите добавить свою функцию или хотите добавить еще одну тестовую строку, введите ping @ZeroPiraeus в чате Python .Таким образом, разница между лучшими и худшими решениями примерно в 50 раз выше для большого набора примеров данных, предоставленных OP здесь (через этот комментарий). Решение Дэвида Чжана - явный победитель, опередив всех остальных примерно в 5 раз по большому набору примеров.
Несколько ответов очень медленные в очень больших случаях «не совпадают». В противном случае функции выглядят одинаково подходящими или явными победителями в зависимости от теста.
Вот результаты, включая графики, сделанные с использованием matplotlib и seaborn, чтобы показать различные дистрибутивы:
Корпус 1 (прилагаемые примеры - небольшой набор)
Корпус 2 (прилагаемые примеры - большой набор)
Корпус 3 (крайние случаи)
Тесты и необработанные результаты доступны здесь .
источник
Решение без регулярных выражений:
Более быстрое решение без регулярных выражений, благодаря @ThatWeirdo (см. Комментарии):
Вышеупомянутое решение очень редко медленнее, чем оригинал, на несколько процентов, но обычно оно работает намного быстрее, иногда намного быстрее. Это все еще не быстрее, чем давидизм для более длинных строк, и решение нулевого регулярного выражения лучше для коротких строк. Он получается самым быстрым (согласно тесту Давидизма на github - см. Его ответ) со строками из 1000-1500 символов. Несмотря на это, он надежно второй (или лучше) во всех случаях, которые я тестировал. Спасибо, ThatWeirdo.
Тестовое задание:
Результаты:
источник
repeat('aa')
возвращаетсяNone
len(string[0:i])
всегда равноi
(в данном случае как минимум). Замена их, а также сохранениеlen(string)
иstring[0:i]
в переменных может ускорить процесс. Также ИМО, это отличное решение,Сначала разделите строку пополам, если это дубликат «2 части». Это уменьшает пространство поиска, если есть четное количество повторов. Затем, работая вперед, чтобы найти наименьшую повторяющуюся строку, проверьте, приводит ли разделение полной строки к все большей и большей подстроке только пустыми значениями.
length // 2
Нужно проверять только подстроки, так как все, что не имеет повторений.Возвращает самое короткое совпадение или None, если совпадения нет.
источник
Проблема также может быть решена
O(n)
в худшем случае с помощью функции префикса.Обратите внимание, это может быть медленнее , в общем случае (UPD: и гораздо медленнее) , чем другие решения , которые зависят от количества делителей
n
, но как правило , найти не удается раньше, я думаю , что одна из плохих случаев для них будетaaa....aab
, где естьn - 1 = 2 * 3 * 5 * 7 ... *p_n - 1
a
«sПрежде всего вам нужно вычислить префиксную функцию
тогда либо нет ответа, либо самый короткий период
и вы просто должны проверить, если
k != n and n % k == 0
(еслиk != n and n % k == 0
тогда ответs[:k]
, иначе нет ответаВы можете проверить доказательство здесь (на русском языке, но онлайн-переводчик, вероятно, добьется цели)
источник
prefix_function()
не действует Python: у вас есть недостающие колоны на вашиwhile
иif
заявлениях, а&&
вместоand
. После исправления, это не сUnboundLocalError: local variable 'i' referenced before assignment
из-за линииfor i in range(i, n):
.prefix_function()
чтобы вернуть аналогичные результаты для других ответов - либо самая короткая подстрока илиNone
- я включу ее в пересмотренный тест, который я собираю.Эта версия пробует только те возможные длины последовательностей, которые являются факторами длины строки; и использует
*
оператор для построения строки полной длины из последовательности-кандидата:Спасибо TigerhawkT3 за то, что заметил, что
length // 2
без этого не+ 1
сможет соответствоватьabab
делу.источник
range
пределlength//2
, как и у меня - вы должны изменить его на,length//2+1
если вы хотите ловить строки, которые в точности удваиваются (например'aabaab'
).Вот прямое решение, без регулярных выражений.
Для подстрок,
s
начинающихся с нулевого индекса, длиной от 1 до 1len(s)
, проверьте, является ли эта подстрокаsubstr
повторяющимся шаблоном. Эта проверка может быть выполнена путем конкатенацииsubstr
с собственнымиratio
временами, так что длина сформированной строки равна длинеs
. Отсюдаratio=len(s)/len(substr)
.Возврат, когда первая подобная подстрока найдена. Это обеспечит наименьшую возможную подстроку, если она существует.
источник
Я начал с более чем восьми решений этой проблемы. Некоторые были основаны на регулярном выражении (match, findall, split), некоторые - нарезке строк и тестировании, а некоторые - на строковых методах (find, count, split). У каждого были преимущества в ясности кода, размере кода, скорости и потреблении памяти. Я собирался опубликовать свой ответ здесь, когда заметил, что скорость выполнения была оценена как важная, поэтому я сделал больше тестов и улучшений, чтобы прийти к следующему:
Этот ответ кажется похожим на некоторые другие ответы здесь, но он имеет несколько оптимизаций скорости, которые другие не использовали:
xrange
немного быстрее в этом приложении,s[:n]
напрямую, мы избегаем создания переменной в каждом цикле.Мне было бы интересно посмотреть, как это работает в стандартных тестах с обычным оборудованием. Я полагаю, что в большинстве тестов он будет намного хуже превосходного алгоритма Дэвида Чжана, но в противном случае он должен быть довольно быстрым.
Я нашел эту проблему очень нелогичной. Решения, которые я думал, будут быстрыми и медленными. Решения, которые выглядели медленными, были быстрыми! Кажется, что создание строк в Python с помощью оператора умножения и сравнения строк очень оптимизировано.
источник
statistics
модуль), поэтому мне пришлось изменить ваши/
s на//
s и заменитьxrange()
наrange()
(который ведет себя как 2.xxrange()
в 3.x).//
в 3.x - целочисленное деление (точно так же, как в 2.x/
), в то время как 3.x -/
это деление на числа с плавающей точкой (что, я уверен, будет намного медленнее, даже если оно не нарушит ваше решение, вызвав попытку использования Поплавок в качестве индекса). Как уже упоминалось, 3.x -range()
это то же самое, что и 2.xxrange()
;range()
в 3.x не существует эквивалента 2.x. Так что я не думаю, что это является причиной какого-либо несоответствия между эталонным тестом и любыми временами, которые вы сделали. Вероятно, просто 3.x медленнее, чем 2.x (или, возможно, ваша машина быстрее, чем моя).Эта функция выполняется очень быстро (протестировано и в 3 раза быстрее, чем самое быстрое решение здесь для строк длиной более 100 тыс. Символов, и чем больше повторяющийся шаблон, тем больше разница). Он пытается минимизировать количество сравнений, необходимых для получения ответа:
Обратите внимание, что, например, для строки длины 8 она проверяет только фрагмент размера 4 и не нуждается в дальнейшем тестировании, поскольку шаблон длины 1 или 2 приведет к повторению шаблона длины 4:
источник
В ответе Дэвида Чжана, если у нас есть какой-то круговой буфер, это не сработает:
principal_period('6210045662100456621004566210045662100456621')
из-за старта621
, где мне бы хотелось, чтобы он выплевывал:00456621
.Расширяя его решение, мы можем использовать следующее:
источник
Вот код на python, который проверяет повторение подстроки в основной строке, заданной пользователем .
Вход :
Выход :
Вход :
Выход :
источник