Làm thế nào tôi có thể biết nếu một chuỗi lặp lại trong Python?


352

Tôi đang tìm cách để kiểm tra xem một chuỗi đã cho có lặp lại chính nó cho toàn bộ chuỗi hay không.

Ví dụ:

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

là các chuỗi lặp lại chính chúng, và

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

là những ví dụ về những cái không.

Các phần lặp lại của chuỗi tôi đưa ra có thể khá dài và bản thân các chuỗi có thể có 500 ký tự trở lên, do đó, việc lặp qua từng ký tự cố gắng xây dựng một mẫu sau đó kiểm tra mẫu so với phần còn lại của chuỗi có vẻ rất chậm. Nhân số đó với khả năng hàng trăm chuỗi và tôi không thể thấy bất kỳ giải pháp trực quan nào.

Tôi đã xem xét các biểu thức một chút và chúng có vẻ tốt khi bạn biết bạn đang tìm kiếm gì, hoặc ít nhất là độ dài của mẫu bạn đang tìm kiếm. Thật không may, tôi không biết.

Làm thế nào tôi có thể biết nếu một chuỗi đang lặp lại chính nó và nếu nó là, chuỗi lặp lại ngắn nhất là gì?


15
lặp qua từng ký tự cố gắng xây dựng một mẫu sau đó kiểm tra mẫu so với phần còn lại của chuỗi có vẻ chậm khủng khiếp - nhưng có phải vậy không?
Tim


2
@AvinashRaj Đó chỉ là một phần của chuỗi, không phải là toàn bộ.
Giăng

11
@AvinashRaj OP đang hỏi về tất cả các giải pháp có thể. Câu hỏi bạn liên kết đến chỉ chấp nhận giải pháp regex . Lưu ý rằng regex có thể giải quyết vấn đề nhưng trong nhiều thời gian hơn mức cần thiết. Ví dụ, một giải pháp tối ưu (tức là thời gian tuyến tính) sẽ sử dụng cây hậu tố của văn bản. Bạn chỉ cần tìm chuỗi con lặp lại dài nhất và thực hiện một số kiểm tra về độ dài.
Bakuriu

2
@ TigerhawkT3 Bộ dữ liệu thực sự quá lớn và khó sử dụng, nhưng các ví dụ trong câu hỏi là một phần của nó, và nếu bạn muốn, đây là một số chi tiết .
Giăng

Câu trả lời:


570

Đây là một giải pháp ngắn gọn để tránh các biểu thức chính quy và các vòng lặp chậm trong Python:

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

Xem câu trả lời của Wiki cộng đồng được bắt đầu bởi @davidism để biết kết quả điểm chuẩn. Tóm tắt,

Giải pháp của David Zhang là người chiến thắng rõ ràng, vượt trội hơn tất cả những người khác ít nhất 5x cho tập hợp ví dụ lớn.

(Đó là câu trả lời, không phải của tôi.)

Điều này dựa trên sự quan sát rằng một chuỗi là định kỳ khi và chỉ khi nó bằng với một vòng quay không cần thiết của chính nó. Kudos để @AleksiTorhamo để thực hiện điều đó thì chúng ta có thể phục hồi trong giai đoạn chủ yếu từ các chỉ số về sự xuất hiện đầu tiên của snăm (s+s)[1:-1], và cho thông báo cho tôi về tùy chọn startendlập luận của Python string.find.


19
Bạn có thể mở rộng điều này để tìm chuỗi lặp lại ngắn nhất bằng cách sử dụng .find()hoặc .index()thay vì in, ví dụ. (s+s).find(s, 1, -1).
Aleksi Torhamo

11
Ngoài ra, tôi nghĩ rằng (s+s).find(s, 1, -1)sẽ nhanh hơn (rất ít) so với (s+s)[1:-1].find(s), ít nhất là đối với các chuỗi lớn hơn, vì việc cắt có nghĩa là bạn phải tạo một bản sao khác (gần) toàn bộ chuỗi.
Aleksi Torhamo

13
Nó giống như nếu bạn lấy một sóng sin hoặc cos từ một hàm tuần hoàn và dịch chuyển nó sang phải. Vì nó hoàn toàn định kỳ, các sóng cuối cùng sẽ khớp hoàn hảo ... Các phép toán tương tự với giải pháp này chỉ là hiện tượng. :) Tôi ước tôi có thể cung cấp cho bạn + upvotes.
Shashank

6
Guido của bản cập nhật gần đây để PEP 8 là có liên quan ở đây: "Hãy kiên định trong báo cáo lợi nhuận Hoặc tất cả các báo cáo trở lại trong một chức năng nên trả lại một biểu hiện, hoặc không ai trong số họ cần.. Nếu bất kỳ lợi nhuận lệnh return một biểu thức, bất kỳ tuyên bố trở lại nơi không có giá trị là được trả về nên nêu rõ đây là trả về Không, và một câu lệnh trả lại rõ ràng sẽ xuất hiện ở cuối hàm (nếu có thể truy cập được). "
Zero Piraeus

8
@WayneConrad Lấy một chuỗi, giả sử, "abcd"bật ra ký tự bên phải và dán lại vào bên trái để lấy "dabc". Thủ tục này được gọi là xoay một chuỗi sang phải 1 ký tự . Lặp lại nthời gian để xoay một chuỗi sang phải theo các nký tự. Bây giờ hãy quan sát rằng nếu chúng ta có một chuỗi kký tự, xoay sang phải bởi bất kỳ bội số nào của kchuỗi không thay đổi. Một vòng quay không cần thiết của một chuỗi là một chuỗi có số ký tự không phải là bội số của độ dài của chuỗi.
David Zhang

181

Đây là một giải pháp sử dụng các biểu thức thông thường.

import re

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

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

Lặp lại các ví dụ trong câu hỏi:

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)

... tạo ra kết quả này:

'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.

Biểu thức chính quy (.+?)\1+$được chia thành ba phần:

  1. (.+?)là một nhóm phù hợp chứa ít nhất một (nhưng càng ít càng tốt) của bất kỳ nhân vật nào (vì +?không tham lam ).

  2. \1+ kiểm tra ít nhất một lần lặp lại của nhóm phù hợp trong phần đầu tiên.

  3. $kiểm tra phần cuối của chuỗi, để đảm bảo rằng không có nội dung bổ sung, không lặp lại sau các chuỗi con lặp lại (và sử dụng re.match()đảm bảo rằng không có văn bản không lặp lại trước các chuỗi con lặp lại).

Trong Python 3.4 trở lên, bạn có thể bỏ $và sử dụng re.fullmatch()thay vào đó, hoặc (trong bất kỳ Python nào ít nhất là từ 2.3) đi theo cách khác và sử dụng re.search()với regex ^(.+?)\1+$, tất cả đều phù hợp với sở thích cá nhân hơn bất kỳ thứ gì khác.


6
Tôi không biết tại sao hai lớp lót ngắn gọn này không phải là câu trả lời được bình chọn cao nhất. Các câu trả lời khác không phải là xấu, nhưng câu trả lời này là tốt hơn nhiều. (Nó có thể sử dụng biểu thức chính quy thường xuyên bị chê bai , nhưng tôi có thể kiểm tra câu này dễ hơn nhiều so với các câu trả lời dài hơn nhiều, có đầy đủ các khối lồng nhau, lỗi chính tả, lỗi sai, v.v.) À, tệ hơn là tốt hơn Tôi giả sử.
Paul Draper

9
Tôi nghĩ có hai lý do chính cho điều đó: 1) một số lập trình viên thích toán học hơn họ thích biểu thức và 2) vì việc thay đổi độ dài và tính chất của chuỗi đầu vào khiến các câu trả lời khác nhau kéo theo hiệu suất, chuỗi trường hợp siêu dài (có thể không xuất hiện trong dữ liệu thực) làm cho giải pháp này xuất hiện dưới mức tối ưu.
TigerhawkT3

đôi khi bạn gặp phải định kiến ​​lớn đối với các biểu thức thông thường. Ive đã có 2 người quản lý cấm sử dụng các biểu thức chính quy vì họ đã nghe các biểu thức chính quy là công cụ sai cho công việc. Ngoại trừ hành vi sau đó họ đã tiến hành bằng cách yêu cầu tôi thực hiện một công cụ
regrec

1
@PaulDraper: Đoán xem regex đang làm gì đằng sau hậu trường? nó đang phân tích cú pháp chuỗi và lưu trữ từng phần tử cho đến khi xảy ra sự trùng khớp rõ ràng. Đó là những gì OP statet quá chậm. Vì vậy, chỉ vì nó là 2 lớp nên không có bất kỳ chiến thắng nào.
dhein

2
@Zaibis, tôi thường đồng ý, nhưng đây là giải pháp ngắn nhất và nhanh nhất ( stackoverflow.com/a/29482936/1212596)....Exception cho David, đã được đăng sau khi tôi đưa ra nhận xét đó. Tôi thực sự thích cách tiếp cận của David hơn (thông minh!).
Paul Draper

90

Bạn có thể quan sát rằng để một chuỗi được coi là lặp lại, độ dài của chuỗi phải chia hết cho độ dài của chuỗi lặp lại. Cho rằng, đây là một giải pháp mà tạo ra các ước của chiều dài từ 1để n / 2bao gồm, chia chuỗi gốc vào chuỗi con với chiều dài của các ước, và kiểm tra sự bình đẳng giữa các tập hợp kết quả:

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: Trong Python 3, /toán tử đã thay đổi để thực hiện phép chia float theo mặc định. Để lấy phép intchia từ Python 2, bạn có thể sử dụng //toán tử thay thế. Cảm ơn bạn @ TigerhawkT3 đã chú ý đến điều này.

Các //Thực hiện điều hành nguyên phân ở cả hai Python 2 và Python 3, vì vậy tôi đã cập nhật các câu trả lời để hỗ trợ cả hai phiên bản. Phần mà chúng tôi kiểm tra để xem liệu tất cả các chuỗi con có bằng nhau hay không bây giờ là một hoạt động ngắn mạch bằng cách sử dụng allvà một biểu thức máy phát.

CẬP NHẬT: Để đáp ứng với một thay đổi trong câu hỏi ban đầu, mã hiện đã được cập nhật để trả về chuỗi con lặp lại nhỏ nhất nếu nó tồn tại và Nonenếu không. @godlygeek đã đề xuất sử dụng divmodđể giảm số lần lặp trên trình divisorstạo và mã cũng đã được cập nhật để phù hợp với điều đó. Bây giờ nó trả về tất cả các ước số dương ntheo thứ tự tăng dần, không bao gồm nchính nó.

Cập nhật thêm cho hiệu năng cao: Sau nhiều thử nghiệm, tôi đã đi đến kết luận rằng chỉ cần kiểm tra tính bằng chuỗi có hiệu suất tốt nhất trong số bất kỳ giải pháp cắt hoặc lặp nào trong Python. Vì vậy, tôi đã lấy một chiếc lá ra khỏi cuốn sách của @ TigerhawkT3 và cập nhật giải pháp của tôi. Bây giờ nhanh hơn 6 lần so với trước đây, nhanh hơn đáng kể so với giải pháp của Tigerhawk nhưng chậm hơn David.


3
Giải pháp này thật tuyệt vời. Bạn có thể thay đổi phương pháp ước số để theo mô hình người tiêu dùng sản xuất. Vì vậy, nó mang lại kết quả như họ được tìm thấy. Sẽ là một sự xấu hổ nếu đây không phải là câu trả lời cao nhất. Mọi thứ khác là vũ phu.
JustinDanielson

3
@JustinDanielson Nó trả về một đối tượng trình tạo được tạo từ biểu thức trình tạo, là một nhà sản xuất ngầm :) Nó sẽ lười đánh giá các ước số.
Shashank

1
Ồ Tôi không biết điều đó. Vâng, thậm chí còn tốt hơn sau đó. : DI hiểu lý do tại sao bạn muốn tránh sqrt, nhưng bạn có thể sử dụng n / 2 làm giới hạn trên cho phạm vi chia.
JustinDanielson

1
@JustinDanielson Cảm ơn bạn đã gợi ý, phạm vi giới hạn trên hiện đã (n/2)bao gồm.
Shashank

1
Nên n / 2divisors()được n // 2?
TigerhawkT3

87

Dưới đây là một số điểm chuẩn cho các câu trả lời khác nhau cho câu hỏi này. Có một số kết quả đáng ngạc nhiên, bao gồm hiệu suất cực kỳ khác nhau tùy thuộc vào chuỗi được thử nghiệm.

Một số hàm đã được sửa đổi để hoạt động với Python 3 (chủ yếu bằng cách thay thế /bằng //để đảm bảo phân chia số nguyên). Nếu bạn thấy có gì đó không đúng, muốn thêm chức năng của bạn hoặc muốn thêm một chuỗi thử nghiệm khác, hãy ping @ZeroPiraeus trong phòng chat Python .

Tóm lại: có sự khác biệt khoảng 50 lần giữa các giải pháp hiệu quả nhất và kém nhất cho tập hợp dữ liệu mẫu lớn do OP cung cấp tại đây (thông qua nhận xét này ). Giải pháp của David Zhang là người chiến thắng rõ ràng, vượt trội so với tất cả những người khác khoảng 5x cho tập hợp ví dụ lớn.

Một vài câu trả lời rất chậm trong các trường hợp "không khớp" cực kỳ lớn. Mặt khác, các chức năng dường như được kết hợp như nhau hoặc người chiến thắng rõ ràng tùy thuộc vào bài kiểm tra.

Dưới đây là kết quả, bao gồm các lô được thực hiện bằng matplotlib và seaborn để hiển thị các bản phân phối khác nhau:


Corpus 1 (ví dụ được cung cấp - tập nhỏ)

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

Đồ thị Corpus 1


Corpus 2 (ví dụ được cung cấp - bộ lớn)

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

Đồ thị Corpus 1


Corpus 3 (trường hợp cạnh)

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

Đồ thị Corpus 3


Các xét nghiệm và kết quả thô có sẵn ở đây .


37

Giải pháp phi 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]

Giải pháp phi regex nhanh hơn, nhờ @That Weirdo (xem bình luận):

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

Giải pháp trên rất hiếm khi chậm hơn so với ban đầu vài phần trăm, nhưng nó thường nhanh hơn một chút - đôi khi nhanh hơn rất nhiều. Nó vẫn không nhanh hơn davidism cho các chuỗi dài hơn và giải pháp regex của zero là tốt hơn cho các chuỗi ngắn. Nó xuất hiện nhanh nhất (theo thử nghiệm của davidism trên github - xem câu trả lời của anh ấy) với chuỗi khoảng 1000-1500 ký tự. Bất kể, nó đáng tin cậy nhanh thứ hai (hoặc tốt hơn) trong tất cả các trường hợp tôi đã thử nghiệm. Cảm ơn, Thatweirdo.

Kiểm tra:

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

Các kết quả:

009
2547
abcde
None
None
None

Đây không phải là một giải pháp vũ phu?
JustinDanielson

7
@JustinDanielson Vâng. Nhưng một giải pháp không hơn không kém.
Điểm chìm

3
Tôi đang thấy khoảng 1e-5 đến 3e-5 giây cho các chuỗi ngắn, 3e-5 đến 4e-5 giây cho các chuỗi dài (1000 ký tự) thành công và một chút dưới một phần nghìn giây cho các chuỗi dài không thành công (trường hợp xấu nhất) . Vì vậy, một ngàn chuỗi 1000 ký tự sẽ mất khoảng một giây. So với câu trả lời toán học, điều này tìm thấy kết quả khớp nhanh hơn 10 lần, nhưng mất 3 lần lâu hơn để thất bại.
TigerhawkT3

repeat('aa')trở vềNone
Tom Cornebize

2
len(string[0:i])luôn luôn bằng i(trong trường hợp này ít nhất). Thay thế chúng, và cũng tiết kiệm len(string)string[0:i]trong các biến có thể tăng tốc mọi thứ. Ngoài ra IMO đây là một giải pháp tuyệt vời, tuyệt vời;)
That Weirdo

24

Đầu tiên, giảm một nửa chuỗi miễn là nó trùng lặp "2 phần". Điều này làm giảm không gian tìm kiếm nếu có số lần lặp chẵn. Sau đó, làm việc chuyển tiếp để tìm chuỗi lặp lại nhỏ nhất, kiểm tra xem việc tách chuỗi đầy đủ bằng chuỗi phụ ngày càng lớn hơn có dẫn đến các giá trị trống không. Chỉ các chuỗi con trở lên length // 2cần phải được kiểm tra vì mọi thứ sẽ không lặp lại.

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

Điều này trả về trận đấu ngắn nhất hoặc Không có nếu không có trận đấu.


16

Vấn đề cũng có thể được giải quyết trong O(n)trường hợp xấu nhất với chức năng tiền tố.

Lưu ý, nó có thể chậm hơn trong trường hợp chung (CẬP NHẬT: và chậm hơn nhiều) so với các giải pháp khác phụ thuộc vào số lượng ước của n, nhưng thường tìm thấy thất bại sớm hơn, tôi nghĩ một trong những trường hợp xấu đối với họ sẽ là aaa....aab, nơi cón - 1 = 2 * 3 * 5 * 7 ... *p_n - 1 a 's

Trước hết bạn cần tính hàm tiền tố

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

sau đó hoặc không có câu trả lời hoặc khoảng thời gian ngắn nhất là

k = len(s) - prefix_function(s[-1])

và bạn chỉ cần kiểm tra xem k != n and n % k == 0(nếu k != n and n % k == 0sau đó trả lời làs[:k] , không có câu trả lời

Bạn có thể kiểm tra bằng chứng ở đây (bằng tiếng Nga, nhưng người dịch trực tuyến có thể sẽ thực hiện thủ thuật)

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 của bạn không hợp lệ: bạn đã thiếu dấu hai chấm trên các câu lệnh whileifcâu lệnh của bạn và &&thay vào đó and. Sau khi sửa chúng, nó không thành công UnboundLocalError: local variable 'i' referenced before assignmentvì dòng này for i in range(i, n):.
Zero Piraeus

Cảm ơn :-) Nếu bạn có thể kết hợp một hàm sử dụng hàm của bạn prefix_function()để trả về kết quả tương tự cho các câu trả lời khác - hoặc là chuỗi con ngắn nhất hoặc None- Tôi sẽ đưa nó vào điểm chuẩn đã sửa đổi.
Zero Piraeus

@ZeroPiraeus, Nó thực sự hoạt động tốt, tôi chỉ gọi sai cách
Rịa

16

Phiên bản này chỉ thử những độ dài chuỗi ứng cử viên là các yếu tố của độ dài chuỗi; và sử dụng *toán tử để xây dựng chuỗi có độ dài đầy đủ từ chuỗi ứng cử viên:

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

Cảm ơn TigerhawkT3 vì đã nhận thấy rằng length // 2không + 1ababtrường hợp nào không phù hợp với trường hợp.


Giải pháp này thực sự gần giống với giải pháp tối ưu của tôi. Tôi thấy rằng bạn có một rangegiới hạn length//2, giống như tôi đã làm - bạn phải thay đổi điều đó thành length//2+1nếu bạn muốn bắt các chuỗi được nhân đôi chính xác (ví dụ 'aabaab').
TigerhawkT3

Và bây giờ chúng giống hệt nhau! \ o / Tôi cần chú ý nhiều hơn đến việc tối ưu hóa trong tương lai, nhưng tôi rất vui vì thuật toán tự nó là âm thanh.
TigerhawkT3

15

Đây là một giải pháp thẳng về phía trước, không có regexes.

Đối với các chuỗi con sbắt đầu từ chỉ số zeroth, có độ dài từ 1 đến len(s), hãy kiểm tra xem chuỗi con đó có phải substrlà mẫu lặp lại không. Kiểm tra này có thể được thực hiện bằng cách nối substrvới ratiothời gian của chính nó , sao cho độ dài của chuỗi do đó hình thành bằng với độ dài của s. Do đó ratio=len(s)/len(substr).

Quay trở lại khi chuỗi con đầu tiên được tìm thấy. Điều này sẽ cung cấp chuỗi con nhỏ nhất có thể, nếu tồn tại.

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"

Bây giờ tôi xem xét cái này một cách cẩn thận, nó dường như gần giống với giải pháp được đăng ban đầu của tôi (trước mọi chỉnh sửa), với sự khác biệt duy nhất là tiết kiệm độ dài và chuỗi con. Tôi đoán tôi đã có một thuật toán khá tốt. : P
TigerhawkT3

@ TigerhawkT3 Vâng thực sự! :)
Saksham Varma

9

Tôi bắt đầu với hơn tám giải pháp cho vấn đề này. Một số là cơ sở trên regex (khớp, findall, split), một số cắt và kiểm tra chuỗi, và một số với các phương thức chuỗi (find, Count, split). Mỗi cái đều có lợi ích về độ rõ của mã, kích thước mã, tốc độ và mức tiêu thụ bộ nhớ. Tôi sẽ đăng câu trả lời của mình lên đây khi nhận thấy tốc độ thực thi được xếp hạng là quan trọng, vì vậy tôi đã thử nghiệm và cải thiện nhiều hơn để đạt được điều này:

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]

Câu trả lời này có vẻ giống với một vài câu trả lời khác ở đây, nhưng nó có một vài tối ưu hóa tốc độ mà những người khác chưa sử dụng:

  • xrange nhanh hơn một chút trong ứng dụng này,
  • nếu một chuỗi đầu vào có độ dài lẻ, không kiểm tra bất kỳ chuỗi con có độ dài chẵn nào,
  • bằng cách sử dụng s[:n]trực tiếp, chúng tôi tránh tạo ra một biến trong mỗi vòng lặp.

Tôi sẽ quan tâm để xem làm thế nào điều này thực hiện trong các thử nghiệm tiêu chuẩn với phần cứng phổ biến. Tôi tin rằng nó sẽ thiếu thuật toán xuất sắc của David Zhang trong hầu hết các bài kiểm tra, nhưng sẽ khá nhanh nếu không.

Tôi thấy vấn đề này rất phản trực giác. Các giải pháp tôi nghĩ sẽ nhanh là chậm. Các giải pháp có vẻ chậm là nhanh! Có vẻ như việc tạo chuỗi của Python với toán tử nhân và so sánh chuỗi được tối ưu hóa cao.


Không tệ chút nào :-) Điểm chuẩn chạy trên Python 3.4 (một phần vì OP không chỉ định phiên bản và đó là những gì mọi người nên sử dụng, và một phần vì nó sử dụng statisticsmô-đun mới ), vì vậy tôi phải thay đổi /s của bạn thành //s và thay thế xrange()bằng range()(hoạt động như 2.x's xrange()trong 3.x).
Zero Piraeus

Dưới đây là các sửa đổi đối với điểm chuẩn, do đó, bạn có thể xem lại các thay đổi của tôi.
Zero Piraeus

Cảm ơn Zero. Đó là nhanh chóng. Kết quả hơi giảm so với dự đoán của tôi. Tôi nghi ngờ các kỹ thuật tôi sử dụng cho tốc độ trong Python 2.7 không hiệu quả lắm trong Python 3.4. Oh, tốt - một bài tập vui vẻ và giáo dục.
Logic Knight

//trong 3.x là phân chia số nguyên (giống như hành vi 2.x của /), trong khi 3.x /là phân chia float (mà tôi chắc chắn sẽ chậm hơn nhiều ngay cả khi nó không phá vỡ giải pháp của bạn bằng cách gây ra nỗ lực sử dụng một float như một chỉ số). Như đã đề cập, 3.x range()là tương tự như 2.x's xrange(); không có tương đương với 2.x's range()tồn tại trong 3.x. Vì vậy, tôi không nghĩ đó là nguyên nhân của bất kỳ sự khác biệt nào giữa điểm chuẩn và bất kỳ thời gian nào bạn thực hiện. Có lẽ chỉ là 3.x chậm hơn so với 2.x (hoặc có thể máy của bạn nhanh hơn của tôi).
Zero Piraeus

Khi tôi có thời gian, tôi sẽ có một cái nhìn cận cảnh về sự khác biệt về thời gian chạy giữa Python 2 và Python 3.
Logic Knight

2

Hàm này chạy rất nhanh (đã được thử nghiệm và nhanh hơn 3 lần so với giải pháp nhanh nhất ở đây trên các chuỗi có hơn 100 nghìn ký tự và sự khác biệt càng lớn khi mẫu lặp lại càng dài). Nó cố gắng giảm thiểu số lượng so sánh cần thiết để có câu trả lời:

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]

Lưu ý rằng ví dụ đối với chuỗi có độ dài 8, nó chỉ kiểm tra đoạn có kích thước 4 và không phải kiểm tra thêm vì mẫu có độ dài 1 hoặc 2 sẽ dẫn đến việc lặp lại mẫu có độ dài 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'

Cảm ơn bạn :) Tôi đã không tối ưu hóa nó nhiều mặc dù. Tôi chỉ muốn trình bày cách tiếp cận khác nhau vì các câu trả lời khác đang tập trung vào việc tìm ra mẫu và cách tiếp cận của tôi tập trung vào việc chứng minh rằng không có mẫu nào :) Vì vậy, đối với các chuỗi ngẫu nhiên, thuật toán của tôi sẽ chạy nhanh hơn nhiều.
Piotr Dabkowski

0

Trong câu trả lời của David Zhang nếu chúng ta có một số loại bộ đệm tròn thì điều này sẽ không hoạt động: principal_period('6210045662100456621004566210045662100456621')do sự khởi đầu 621, nơi tôi sẽ thích nó để nhổ ra:00456621 .

Mở rộng giải pháp của mình, chúng ta có thể sử dụng như sau:

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

Dưới đây là mã trong python kiểm tra sự lặp lại của chuỗi phụ trong chuỗi chính do người dùng cung cấp .

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)

Đầu vào :

0045662100456621004566210045662100456621

Đầu ra :

Độ dài chuỗi của bạn: 40

Chuỗi con '00456621' lặp lại trong chuỗi '0045662100456621004566210045662100456621'

Đầu vào :

004608294930875576036866359447

Đầu ra :

Độ dài chuỗi của bạn: 30

Không tìm thấy Chuỗi con lặp lại trong chuỗi '004608294930875576036866359447'

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.