파이썬에서 문자열이 반복되는지 어떻게 알 수 있습니까?


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
패턴을 작성하려고 시도하는 각 문자를 반복 한 다음 패턴 대 문자열의 나머지 부분을 확인하는 것이 끔찍한 것처럼 보이지만 그렇지 않습니까?
Tim


2
@AvinashRaj 문자열의 일부만 일치하지만 전체가 아닙니다.
John

11
@AvinashRaj OP는 가능한 모든 솔루션에 대해 묻고 있습니다. 링크 된 질문은 정규식 솔루션 만 허용 합니다. 정규식은 문제를 해결할 수 있지만 필요한 시간보다 훨씬 더 많은 시간이 걸릴 수 있습니다. 예를 들어 최적의 솔루션 (즉, 선형 시간)은 텍스트의 접미사 트리를 사용합니다. 가장 긴 반복 부분 문자열을 찾아서 길이를 확인해야합니다.
Bakuriu

2
@ TigerhawkT3 실제 데이터 세트는 너무 크고 다루기 어렵지만 문제의 예는 그 일부이며 원하는 경우 여기에 더 있습니다.
John

답변:


570

다음은 정규 표현식을 피하고 Python 내부 루프를 느리게하는 간결한 솔루션입니다.

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

벤치 마크 결과는 @davidism에서 시작한 Community Wiki 답변을 참조하십시오 . 요약하자면,

David Zhang의 솔루션은 분명한 승자이며, 큰 예제 세트에서 다른 모든 제품보다 5 배 이상 뛰어납니다.

(그것의 대답은 내 것이 아닙니다.)

이것은 문자열 자체가 사소한 회전과 동일한 경우에만 문자열이 주기적이라는 관찰에 기반합니다. sin 의 첫 번째 색인에서 주요 기간을 복구하고 Python의 (s+s)[1:-1]선택적 startend인수 를 알려주 는 @AleksiTorhamo에게 Kudos string.find.


19
예를 들어 .find()또는 .index()대신 에를 사용하여 가장 짧은 반복 하위 시퀀스를 찾도록이 길이를 연장 할 수 있습니다 in. (s+s).find(s, 1, -1).
Aleksi Torhamo

11
또한 슬라이싱은 전체 문자열의 (거의) 다른 사본을 만들어야하기 때문에 적어도 큰 문자열의 (s+s).find(s, 1, -1)경우보다 (아주 약간) 빠를 것이라고 생각 (s+s)[1:-1].find(s)합니다.
Aleksi Torhamo

13
주기적 함수 에서 사인파를 가져 와서 오른쪽으로 옮기는 것과 같습니다. 완전히 주기적이기 때문에 파도는 결국 완벽하게 일치합니다 ...이 솔루션과 평행 한 수학은 경이 롭습니다. :) 나는 당신에게 + ∞ 공감을 줄 수 있기를 바랍니다.
Shashank

6
귀도의 최신 업데이트PEP 8은 여기에 관련이 "반환 진술에 일관성 어느 함수의 모든 반환 문은 식을 반환해야합니다, 또는 그들 중 누구도해야한다.. 어떤 return 문을 반환하는 경우 표현식 값은 어떤 반환 문없는 곳에 return은이를 명시 적으로 Return None 으로 표시해야하며, 함수의 끝에 명시적인 return 문이 있어야합니다 (연결 가능한 경우). "
Zero Piraeus

8
@WayneConrad 문자열을 가져 와서 "abcd"오른쪽의 문자를 튀어 나와 왼쪽에 다시 붙여서을 얻습니다 "dabc". 이 절차를 문자열을 오른쪽으로 1 문자 회전시키는 것 입니다. n문자열을 오른쪽으로 회전 하려면 시간을 반복하십시오 n. 이제 문자열이 k여러 개인 경우 오른쪽으로 여러 번 회전 k하면 문자열이 변경되지 않습니다. 문자열 의 사소한 회전은 문자 번호가 문자열 길이의 배수가 아닌 회전입니다.
David Zhang

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()대신 사용을 삭제 하거나 (최소한 2.3까지는 Python에서) 다른 방법으로 이동 re.search()하여 정규식과 함께 사용하면 다른 ^(.+?)\1+$모든 것보다 개인적인 취향에 더 가깝습니다.


6
나는이 간결한 두 라이너가 왜 가장 투표가 많은 대답이 아닌지 전혀 모른다. 다른 답변은 나쁘지 않지만이 답변이 훨씬 좋습니다. (자주 거부되는 정규 표현식을 사용할 수도 있지만 중첩 된 블록, 잠재적 오타, 오프-투-1 오류 등으로 가득 찬 다른 더 긴 답변보다 훨씬 쉽게 검사 할 수 있습니다. 아, 더 좋지 않습니다. 나는 생각한다.
Paul Draper

9
나는 그 두 가지 주된 이유가 있다고 생각합니다 : 1) 정규 표현식보다 수학과 같은 일부 프로그래머, 2) 입력 문자열의 길이와 특성이 다양하기 때문에 성능에 대해 다른 대답을 이끌어 내기 때문에 슈퍼 롱 엣지 문자열 (실제 데이터에는 표시되지 않을 수도 있음)이 솔루션은 차선책으로 나타납니다.
TigerhawkT3

때때로 당신은 정규 표현에 대해 큰 편견을 겪습니다. 나는 정규 표현식이 작업에 대한 잘못된 도구라고 들었 기 때문에 정규 표현식 사용을 금지하는 2 명의 관리자가있었습니다.
오프 코스를

1
@PaulDraper : 정규식이 무대 뒤에서 무엇을하고 있는지 맞춰보세요? 문자열을 구문 분석하고 가능한 반복 일치가 발생할 때까지 각 요소를 저장합니다. OP OP 상태가 너무 느리다는 것과 같습니다. 2 라이너이기 때문에 성능이 향상되지 않습니다.
dhein

2
@ Zaibis는 일반적으로 동의하지만 이것이 가장 짧고 빠른 솔루션입니다 ( stackoverflow.com/a/29482936/1212596) .... David의 경우를 제외하고는 해당 의견을 작성한 후 게시되었습니다. 나는 실제로 David의 접근 방식이 더 낫습니다 (영리한!).
Paul Draper

90

문자열이 반복되는 것으로 간주 되려면 길이가 반복되는 시퀀스의 길이로 나눌 수 있어야한다는 것을 알 수 있습니다. 다음은 길이의 제수 1n / 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파이썬 2 에서 나누기 위해 //대신 연산자를 사용할 수 있습니다 . 이 점에 주목 해 주신 @ TigerhawkT3에게 감사합니다.

//모두 파이썬이 파이썬 3 부문의 정수 연산자 수행하는, 그래서 내가 두 버전을 모두 지원하는 대답을 업데이 트했습니다. 모든 하위 문자열이 동일한 지 테스트하기 위해 테스트하는 부분은 이제 all발전기 표현식을 사용한 단락 작업 입니다.

업데이트 : 원래 질문의 변경에 대한 응답으로 코드는 존재하는 경우 가장 작은 반복 하위 문자열을 반환하도록 업데이트되었습니다 None. @godlygeek은 생성기 divmod의 반복 횟수를 줄이는 데 사용 하도록 제안 divisors했으며 코드도 일치하도록 업데이트되었습니다. 이제 모든 양의 제수를 n오름차순으로 반환 n합니다.

고성능을위한 추가 업데이트 : 여러 테스트 후 문자열 동등성 테스트만으로 파이썬의 슬라이싱 또는 반복자 솔루션 중에서 최고의 성능을 발휘한다는 결론에 도달했습니다. 따라서 @ TigerhawkT3의 책에서 나뭇잎을 가져 와서 솔루션을 업데이트했습니다. 이전보다 6 배 이상 빠르며, 특히 Tigerhawk 솔루션보다 빠르지 만 David보다 느립니다.


3
이 솔루션은 놀랍습니다. 생산자 소비자 패턴을 따르도록 제수 방법을 변경할 수 있습니다. 결과가 발견되면 결과를 얻습니다. 이것이 가장 높은 대답이 아니라면 부끄러운 일이 될 것입니다. 다른 모든 것은 무차별적인 힘입니다.
JustinDanielson

3
@JustinDanielson 암시 적 생성자 인 생성자 표현식에서 생성 된 생성기 객체를 반환합니다.
Shashank

1
오 나는 몰랐다. 그럼 더 나아 졌어요 : DI는 sqrt를 피하려는 이유를 이해하지만 n / 2를 제수 범위의 상한으로 사용할 수 있습니다.
JustinDanielson

1
@JustinDanielson 제안에 감사드립니다. 범위 상한은 이제 (n/2)포괄적입니다.
Shashank

1
해야 n / 2divisors()n // 2?
TigerhawkT3

87

다음은이 질문에 대한 다양한 답변의 벤치 마크입니다. 테스트되는 문자열에 따라 크게 다른 성능을 포함하여 놀라운 결과가있었습니다.

일부 함수는 Python 3에서 작동하도록 수정되었습니다 (주로 정수 나누기를 보장하기 위해 대체 /하여 //). 뭔가 잘못 보이면 함수를 추가하거나 Python 테스트 룸에서 @ZeroPiraeus를 ping하여 다른 테스트 문자열을 추가하려고합니다 .

요약하자면 여기 에서 OP가 제공 한 대규모 예제 데이터 세트에 대해 최고 성능과 최악 성능의 솔루션간에 약 50 배의 차이가 있습니다 ( 의견을 통해 ). David Zhang의 솔루션 은 확실한 승자이며, 큰 예제 세트에서 다른 모든 제품보다 약 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

위의 솔루션은 원본보다 몇 퍼센트 느리게 진행되는 경우가 드물지만 일반적으로 조금 더 빠르며 때로는 훨씬 빠릅니다. 긴 문자열의 경우 여전히 davidism보다 빠르지 않으며 0의 정규식 솔루션은 짧은 문자열보다 우수합니다. 약 1000-1500 문자의 문자열로 (github에 대한 davidism의 테스트에 따르면-그의 대답을 참조하십시오) 가장 빠릅니다. 어쨌든, 테스트 한 모든 경우에서 확실히 두 번째로 빠릅니다 (또는 더 좋습니다). 고마워, 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 초, 성공적인 긴 (1000 자) 문자열의 경우 3e-5 ~ 4e-5 초, 실패한 긴 문자열의 경우 1 밀리 초 미만 (최악의 경우) . 따라서 1000 개의 1000 자 문자열은 약 1 초가 걸립니다. 수학 답변과 비교하면 일치하는 결과가 10 배 빠르지 만 실패하는 데 3 배 더 걸립니다.
TigerhawkT3

repeat('aa')반환None
Tom Cornebize

2
len(string[0:i])항상 i(이 경우 적어도)와 같습니다. 이를 대체 하고 변수를 저장 len(string)하고 string[0:i]변수를 저장하면 속도가 빨라질 수 있습니다. 또한 IMO는 훌륭한 솔루션입니다.;)
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()유효한 파이썬되지 않습니다 : 당신이 당신의 실종 콜론이 whileif하고, 문을 &&대신 and. 그것들을 수정 한 후에 UnboundLocalError: local variable 'i' referenced before assignment는 라인 때문에 실패합니다 for i in range(i, n):.
Zero Piraeus

감사합니다 :-) prefix_function()유사한 결과를 다른 답변에 반환 하는 함수를 사용 하면 가장 짧은 부분 문자열 또는 가장 가까운 부분 문자열 또는 None수정 된 벤치 마크에 포함시킬 것입니다.
Zero Piraeus

@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

length // 2없이는 케이스 + 1와 일치 하지 않을 것이라는 점을 알아 낸 TigerhawkT3에게 감사합니다 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

이 문제에 대한 8 가지 이상의 솔루션으로 시작했습니다. 일부는 정규 표현식 (일치, 찾기, 분할), 일부 문자열 슬라이싱 및 테스트, 문자열 방법 (찾기, 개수, 분할)을 기반으로합니다. 각각은 코드 선명도, 코드 크기, 속도 및 메모리 소비 측면에서 이점이있었습니다. 실행 속도가 중요하다는 것을 알았을 때 내 대답을 여기에 게시하려고 했으므로 이에 도달하기 위해 더 많은 테스트와 개선을 수행했습니다.

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]직접 사용 하면 각 루프에서 변수를 만들지 않아도됩니다.

일반적인 하드웨어의 표준 테스트에서 이것이 어떻게 수행되는지 알고 싶습니다. 나는 그것이 대부분의 테스트에서 David Zhang의 훌륭한 알고리즘에 미치지 못할 것이라고 생각하지만 그렇지 않으면 상당히 빠릅니다.

나는이 문제가 매우 반 직관적이라는 것을 알았습니다. 내가 빠를 것이라고 생각한 솔루션은 느렸다. 느리게 보인 솔루션은 빠르다! 곱하기 연산자와 문자열 비교를 사용하여 Python의 문자열 생성이 고도로 최적화 된 것 같습니다.


모든 :-)에 나쁜 파이썬 3.4에 대한 벤치 마크 실행 (OP 버전을 지정하지 않고 부분적으로 있기 때문에 그의 무엇 모두 해야 는 새로운 사용하고, 부분적으로 있기 때문에 사용하고있을 statistics나는 당신의 변경했다 모듈), 그래서 /에들 //들 및 교체 xrange()와 함께 range()(이 동작합니다 같은 2.X의 xrange()3.X에서).
Zero Piraeus

다음은 벤치 마크 수정 사항 이므로 변경 사항을 검토 할 수 있습니다.
Zero Piraeus

고마워 제로. 그것은 빠르다. 결과는 내 예측에서 약간 떨어졌습니다. Python 2.7에서 속도에 사용한 기술이 Python 3.4에서별로 효과적이지 않다고 생각합니다. 아, 재미 있고 교육적인 운동입니다.
Logic Knight

//3.x에서 정수 나누기 (의 2.x 동작과 동일 /)이며 3.x /는 float 나누기입니다 (사용하려고 시도하여 솔루션을 중단시키지 않아도 훨씬 느려질 것입니다) 인덱스로서의 float). 언급했듯이 3.x range()는 2.x와 동일합니다 xrange(). range()3.x 에는 2.x에 해당하는 것이 없습니다 . 따라서 이것이 벤치 마크와 타이밍 사이에 차이가 발생했다고 생각하지 않습니다. 아마도 3.x가 2.x보다 전체적으로 느리다는 것입니다 (또는 컴퓨터가 내 것보다 빠릅니다).
Zero Piraeus

시간이 좀 오면 파이썬 2와 파이썬 3의 런타임 차이를 면밀히 살펴볼 것입니다.
Logic Knight

2

이 함수는 매우 빠르게 실행됩니다 (테스트 된 결과 100k자를 초과하는 문자열에서 가장 빠른 솔루션보다 3 배 이상 빠르며 반복 패턴이 길수록 차이가 커집니다). 답을 얻는 데 필요한 비교 횟수를 최소화하려고 시도합니다.

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'

감사합니다 :) 나는 그것을 많이 최적화하지 않았습니다. 다른 답변이 패턴을 찾는 데 중점을두고 다른 접근법을 제시하고 싶었습니다. 내 접근법은 패턴이 없다는 것을 증명하는 데 중점을 둡니다. :) 임의의 문자열의 경우 알고리즘이 훨씬 빠르게 실행되어야합니다.
Piotr Dabkowski

0

David Zhang의 대답에서 우리가 일종의 원형 버퍼를 가지고 있다면 이것이 작동하지 않을 것입니다 : 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

다음은 사용자가 제공 한 기본 문자열에서 하위 문자열의 반복을 확인하는 파이썬 코드입니다 .

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'에 반복 하위 문자열이 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.