Як я можу зрозуміти, чи повторюється рядок у Python?


352

Я шукаю спосіб перевірити, повторюється чи не заданий рядок для цілого рядка чи ні.

Приклади:

[
    '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 і більше символів, тому циклічне проходження кожного символу, намагаючись створити шаблон, потім перевірка шаблону проти решти рядка здається жахливо повільним. Помножте це на потенційно сотні рядків, і я не бачу жодного інтуїтивного рішення.

Я трохи роздивився реджекси, і вони здаються гарними, коли ви знаєте, що шукаєте, або хоча б довжину шаблону, який ви шукаєте. На жаль, я не знаю жодного.

Як я можу визначити, чи повторюється рядок і якщо він є, що таке найкоротше повторення?


15
переглядаючи кожен символ, намагаючись побудувати шаблон, потім перевірка шаблону проти решти рядка здається жахливою повільною - але це?
Тім


2
@AvinashRaj Це лише відповідність частини рядка, а не повна річ.
Джон

11
@AvinashRaj ОП запитує про всі можливі рішення. Питання, на яке ви посилаєтесь, приймає лише рішення регулярного виразу . Зауважте, що регулярне вираження може вирішити проблему, але за набагато більше часу, ніж потрібно. Наприклад, оптимальним рішенням (тобто лінійним часом) буде використовувати суфіксне дерево тексту. Вам просто потрібно знайти найдовшу повторювану підрядку і зробити кілька перевірок на довжину.
Бакуріу

2
@ TigerhawkT3 Справжній набір даних занадто великий і громіздкий, але приклади у питанні є частиною цього, і, якщо ви хочете, ось ще кілька .
Джон

Відповіді:


570

Ось стисле рішення, яке дозволяє уникнути регулярних виразів і повільних циклів у Python:

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

Дивіться відповідь Вікі спільноти, розпочату @davidism, для отримання результатів. Підводячи підсумок,

Рішення Девіда Чжана - явний переможець, що перевершує всіх інших принаймні в 5 разів для великого набору прикладів.

(Слова цієї відповіді, не мої.)

Це ґрунтується на спостереженні, що рядок є періодичним, якщо і лише тоді, коли він дорівнює самому нетривіального обертання. Кудо до @AleksiTorhamo для того, щоб зрозуміти, що потім ми можемо відновити основний період з індексу першого виникнення sв (s+s)[1:-1], і для того, щоб повідомити мене про необов'язкові startта endаргументи Python's string.find.


19
Ви можете розширити це, щоб знайти найкоротший повторюваний підхід, використовуючи .find()або .index()замість in, наприклад, (s+s).find(s, 1, -1).
Алексі Торхамо

11
Крім того, я думаю, що (s+s).find(s, 1, -1)буде (дуже трохи) швидше (s+s)[1:-1].find(s), принаймні, для більших рядків, оскільки нарізка означає, що вам доведеться створити ще одну копію (майже) всього рядка.
Алексі Торхамо

13
Це як якщо ви берете хвилю sin або cos від періодичної функції і зміщуєте її вправо. Оскільки це повністю періодично, хвилі з часом ідеально збігаються ... Математичні паралелі до цього рішення просто феноменальні. :) Я б хотів, щоб я міг вам дати + ∞ нагороди.
Шашанк

6
Тут актуальне останнє оновлення Гідо до PEP 8 : "Будьте послідовними у висловлюваннях зворотного зв’язку. Будь-які всі оператори повернення у функції повинні повертати вираз, або жодне з них не повинно. Якщо будь-який оператор return повертає вираз, будь-які заяви повернення, де значенням немає return повинен прямо вказати це як return None, а явний оператор return повинен бути присутнім в кінці функції (якщо є доступним). "
Нуль Пірей

8
@WayneConrad Візьміть рядок, скажімо, вискочіть "abcd"символ праворуч і приклейте його назад ліворуч, щоб отримати "dabc". Ця процедура називається обертанням рядка праворуч на 1 символ . Повторіть nрази, щоб обертати рядок праворуч за nсимволами. Тепер зауважте, що якщо у нас є рядок kсимволів, що обертається праворуч на будь-яке кратне число, kзалишає рядок незмінною. Нетривіальним обертання струни один персонаж якого число не кратне довжині струни.
Девід Чжан

181

Ось рішення з використанням регулярних виразів.

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) піти в інший бік та використовувати re.search()з регулярним виразом ^(.+?)\1+$, все це більше до особистого смаку, ніж будь-що інше.


6
Я поняття не маю, чому цей стислий два лайнери не є найвищою відповіді. Інші відповіді непогані, але ця набагато краща. (Він може використовувати часто занедбаний регулярний вираз , але я можу перевірити це набагато простіше, ніж інші набагато довші відповіді, які переповнені вкладеними блоками, потенційними помилками друку, помилками один за одним тощо) А ну, гірше, краще Я вважаю.
Пол Дрейпер

9
Я думаю, що для цього є дві основні причини: 1) деяким програмістам подобається математика більше, ніж їм подобається регулярних виразів, і 2) оскільки зміна довжини та характеру вхідних рядків змушує різні відповіді на ефективність, супердовгі крайні рядки випадку (яка може навіть не відображатись у реальних даних), це рішення виглядає неоптимальним.
TigerhawkT3

іноді ви стикаєтеся з великими упередженнями щодо регулярних виразів. У мене було 2 менеджери, які забороняли використовувати регулярні вирази, оскільки вони чули, що регулярні вирази є неправильним інструментом для роботи. За винятком бійців, тоді вони продовжили, попросивши мене реалізувати двигун регулярних
викидів

1
@PaulDraper: Здогадайтесь, що робить регекс поза сценою? він розбирає рядок і зберігає кожен елемент, поки не відбудеться відповідне повторне повторення. Це те саме, що ОП заявляє як занадто повільне. тому тільки тому, що це 2-х вкладиш, не виграє жодна продуктивність.
дехень

2
@Zaibis, я зазвичай згоден, але це і найкоротший і швидке рішення ( stackoverflow.com/a/29482936/1212596)....Except для Девіда, який був опублікований після того, як я зробив це зауваження. Мені насправді більше подобається підхід Девіда (розумний!).
Пол Дрейпер

90

Можна зробити зауваження, що для струни, яку слід вважати повторюваною, її довжина повинна бути поділена на довжину її повторної послідовності. З огляду на , що тут є рішення , яке генерує подільники довжини від 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

EDIT: У Python 3 /оператор змінив поплавковий поділ за замовчуванням. Щоб отримати intподіл від Python 2, ви можете скористатися //оператором. Дякую @ TigerhawkT3 за те, що ви звернули увагу на це.

В //виконує оператор цілочисельного ділення в обох Python 2 і Python 3, тому я оновив відповідь на підтримку обох версій. Частина, в якій ми перевіряємо, чи всі підрядки рівні, тепер є операцією короткого замикання з використанням allта генераторним виразом.

ОНОВЛЕННЯ: У відповідь на зміну вихідного питання код тепер оновлено, щоб повернути найменшу повторювану підрядку, якщо вона існує іNone якщо ні. @godlygeek запропонував використовувати divmodдля зменшення кількості ітерацій на divisorsгенераторі, і код було оновлено відповідно до цього. Тепер він повертає всі позитивні подільники nу порядку зростання, виключаючи nсебе.

Подальше оновлення для високої продуктивності: Після декількох тестів я прийшов до висновку, що просто тестування рівності рядків має найкращі показники з будь-якого рішення нарізки або ітератора в Python. Таким чином, я взяв лист із книги @ TigerhawkT3 та оновив своє рішення. Зараз це вже в 6 разів швидше, ніж раніше, помітно швидше, ніж рішення Тігерхаука, але повільніше, ніж у Девіда.


3
Це рішення дивовижне. Ви можете змінити метод дільника, щоб слідувати схемі виробництва-споживача. Так що це дає результати, як вони знайдені. Буде соромно, якщо це не найвища відповідь. Все інше - груба сила.
JustinDanielson

3
@JustinDanielson Він повертає об'єкт-генератор, створений з генераторного виразу, який є неявним продуцентом :) Він буде ліниво оцінювати дільники.
Шашанк

1
Ох. Я цього не знав. Ну, ще краще тоді. : DI розумію, чому ви хочете уникнути sqrt, але ви можете використовувати n / 2 в якості верхньої межі діапазону дільника.
JustinDanielson

1
@JustinDanielson Дякую за пропозицію, верхня межа діапазону тепер (n/2)включена.
Шашанк

1
Якщо n / 2в divisors()бути n // 2?
TigerhawkT3

87

Ось деякі орієнтири для різних відповідей на це питання. Були деякі дивовижні результати, включаючи дивовижну продуктивність залежно від тестуваної струни.

Деякі функції були модифіковані для роботи з Python 3 (головним чином , шляхом заміни /з , //щоб забезпечити цілочисельне ділення). Якщо ви бачите щось не так, хочете додати свою функцію або хочете додати ще одну тестову рядок, ping @ZeroPiraeus у чаті Python .

Підсумовуючи це: існує приблизно 50-кратна різниця між найкращими та найефективнішими рішеннями для великого набору даних з прикладу, що надаються тут ОП (за допомогою цього коментаря). Рішення Девіда Чжана - явний переможець, перевершуючи всіх інших приблизно в 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

Корпус 1 граф


Корпус 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

Корпус 1 граф


Корпус 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

Графік 3 корпусу


Тести та вихідні результати доступні тут .


37

Рішення без повторного вираження:

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

Вищевказане рішення дуже рідко повільніше, ніж оригінал на кілька відсотків, але, як правило, це трохи швидше - іноді набагато швидше. Це все-таки не швидше, ніж для давидизму для довших рядків, і рішення для регулярних виразів нуля є кращим для коротких рядків. Він виходить найшвидшим (згідно тесту на давідизм на github - див. Його відповідь) з рядками приблизно 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

7
@JustinDanielson Так. Але рішення все ж таки.
Sinkingpoint

3
Я бачу приблизно 1e-5 до 3e-5 секунд для коротких рядків, 3e-5 до 4e-5 секунд для успішних довгих (1000 символів) рядків і трохи менше мілісекунди для невдалих довгих рядків (найгірший випадок) . Тож тисяча 1000-символьних рядків зайняла б секунду. Порівняно з математичною відповіддю, це знаходить відповідність у 10 разів швидше, але на помилку потрібно 3 рази більше.
TigerhawkT3

repeat('aa')повертаєтьсяNone
Том Корнебіз

2
len(string[0:i])завжди дорівнює i(принаймні, у цьому випадку). Заміна цих даних, а також економія len(string)та string[0:i]змінні можуть пришвидшити ситуацію. Також ІМО це чудове рішення, дивовижне;)
ThatWeirdo

24

По-перше, вдвічі вставте рядок, доки це дублікат "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

Це повертає найкоротший збіг або None, якщо немає.


16

Проблема також може бути вирішена 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

Ваш prefix_function()неправдивий Python: у вас відсутні товсті кольори у ваших whileі ifзаявах, а &&замість and. Після їх виправлення це не вдається UnboundLocalError: local variable 'i' referenced before assignmentчерез лінію for i in range(i, n):.
Нульовий Пірей

Спасибі :-) Якщо ви можете скласти функцію, яка використовує ваш, prefix_function()щоб повернути подібні результати до інших відповідей - або найкоротшої підрядки, або None- я включу її до переглянутого еталону, який я складаю разом.
Нульовий Пірей

@ZeroPiraeus, Насправді це працює чудово, я просто називав це неправильно
RiaD

16

Ця версія намагається застосовувати лише ті довжини послідовностей кандидатів, які є чинниками довжини рядка; і використовує *оператор для створення повнорозмірного рядка з послідовності кандидатів:

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випадку.


Це рішення насправді практично ідентичне моєму оптимізованому. Я бачу, що у вас є rangeобмеження length//2, як і я, - ви повинні змінити це, length//2+1якщо ви хочете вловлювати рядки, які точно збільшилися вдвічі (наприклад 'aabaab').
TigerhawkT3

А тепер вони однакові! \ o / мені потрібно приділяти більше уваги оптимізації в майбутньому, але я радий, що сам алгоритм був здоровим.
TigerhawkT3

15

Ось пряме рішення вперед, без регулярних виразів.

Для підрядків, 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"

Тепер, коли я уважно дивлюся на це, здається, він майже ідентичний моєму спочатку розміщеному (до будь-якого редагування) рішенню, з різницею лише збереження довжини та підрядки. Напевно, у мене був досить хороший алгоритм. : P
TigerhawkT3

@ TigerhawkT3 Так, справді! :)
Saksham Varma

9

Я почав з більш ніж восьми рішень цієї проблеми. Деякі були основою на регулярному вираженні (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 за допомогою оператора множення та порівняння рядків дуже оптимізовано.


Непогано :-) Тест працює на Python 3.4 (почасти тому, що в ОР не вказана версія, і це те, що всі повинні використовувати, а почасти тому, що він використовує новий statisticsмодуль), тому мені довелося змінити ваш /s на //s і замінити xrange()на range()(який поводиться як 2.x xrange()у 3.x).
Нуль Пірей

Ось зміни до еталону, так що ви можете до речі переглянути мої зміни.
Нульовий Пірей

Дякую нулю Це було швидко. Результати трохи знизилися на моїх прогнозах. Я підозрюю, що методи, які я використовував для швидкості в Python 2.7, не дуже ефективні в Python 3.4. О, добре - весела та навчальна вправа.
Логічний лицар

//в 3.x - це ціле ділення (як і поведінка 2.x /), а в 3.x /- поплавковий поділ (що, я впевнений, було б набагато повільніше, навіть якби воно не порушило ваше рішення, викликаючи спробу використання поплавок як індекс). Як вже згадувалося, 3.x - range()це те саме, що і 2.x xrange(); немає еквівалента 2.x range()у 3x. Тому я не думаю, що це причина будь-якої невідповідності між базовим показником та будь-якими встановленими ними термінами. Це, мабуть, просто, що 3.x уповільнений, ніж 2.x (а може бути, ваша машина швидша за мою).
Нульовий Пірей

Коли я отримаю трохи часу, я детально ознайомлюся з різницею часу між Python 2 та Python 3.
Логічний лицар

2

Ця функція запускається дуже швидко (протестована, і вона в 3 рази швидша, ніж найшвидше рішення тут на рядках із символами понад 100 К, і різниця стає більшою, чим довше повторювана схема). Він намагається мінімізувати кількість порівнянь, необхідних для отримання відповіді:

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'

Дякую :) Я не дуже оптимізував це. Я просто хотів представити інший підхід, оскільки інші відповіді зосереджуються на пошуку шаблону, а мій підхід зосереджується на доведенні того, що немає шаблону :) Тому для випадкових рядків мій алгоритм повинен працювати набагато швидше.
Пьотр Дабковський

0

У відповіді Девіда Чжана , якщо у нас є якесь - то кільцевої буфер це не буде працювати: 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'

-1

Ось код у 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" не знайдено повторного підрядка "

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.