ฉันจะรู้ได้อย่างไรว่าสตริงซ้ำตัวเองใน 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 ตัวขึ้นไปดังนั้นการวนลูปผ่านตัวละครแต่ละตัวที่พยายามสร้างรูปแบบจากนั้นตรวจสอบรูปแบบกับส่วนที่เหลือของสตริงดูช้ามาก ทวีคูณด้วยจำนวนร้อยสายและฉันไม่สามารถเห็นวิธีแก้ปัญหาที่เข้าใจง่าย

ฉันดู regexes เล็กน้อยและมันดูดีเมื่อคุณรู้ว่าคุณกำลังมองหาอะไรหรืออย่างน้อยก็ความยาวของรูปแบบที่คุณกำลังมองหา น่าเสียดายที่ฉันไม่รู้

ฉันจะรู้ได้อย่างไรว่าสตริงซ้ำตัวเองและถ้าเป็นเช่นนั้นลำดับการทำซ้ำที่สั้นที่สุดคืออะไร?


15
วนตัวอักขระแต่ละตัวที่พยายามสร้างรูปแบบจากนั้นตรวจสอบรูปแบบกับส่วนที่เหลือของสตริงดูเหมือนช้ามาก - แต่มันคืออะไร?
ทิม


2
@AvinashRaj นั่นเป็นเพียงการจับคู่ส่วนหนึ่งของสตริงไม่ใช่สิ่งที่เต็ม
จอห์น

11
@AvinashRaj OP กำลังถามถึงแนวทางแก้ไขที่เป็นไปได้ คำถามที่คุณเชื่อมโยงเพื่อยอมรับโซลูชันของregexเท่านั้น โปรดทราบว่า regex อาจสามารถแก้ปัญหาได้ แต่ใช้เวลานานกว่าที่จำเป็น ตัวอย่างเช่นทางออกที่ดีที่สุด (เช่นเวลาเชิงเส้น) จะใช้ต้นไม้ต่อท้ายของข้อความ คุณเพียงแค่ต้องค้นหาสตริงย่อยซ้ำที่ยาวที่สุดและทำการตรวจสอบความยาว
Bakuriu

2
@ TigerhawkT3 ชุดข้อมูลที่แท้จริงอยู่ไกลขนาดใหญ่เกินไปและเทอะทะ แต่ตัวอย่างในคำถามที่เป็นส่วนหนึ่งของมันและถ้าคุณต้องการที่นี่บางมากขึ้น
จอห์น

คำตอบ:


570

ต่อไปนี้เป็นโซลูชันที่กระชับซึ่งหลีกเลี่ยงนิพจน์ทั่วไปและลูปใน Python ที่ช้า:

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

ดูCommunity Wiki คำตอบที่เริ่มต้นโดย @davidism สำหรับผลลัพธ์การวัดประสิทธิภาพ สรุป,

โซลูชันของ David Zhang เป็นผู้ชนะที่ชัดเจนโดยมีประสิทธิภาพเหนือกว่าผู้อื่นทั้งหมดอย่างน้อย 5x สำหรับชุดตัวอย่างขนาดใหญ่

(คำพูดของคำตอบนั้นไม่ใช่ของฉัน)

สิ่งนี้ขึ้นอยู่กับการสังเกตว่าสตริงเป็นระยะถ้าหากเท่ากับการหมุนแบบไม่หมุนรอบตัวเอง รุ่งโรจน์เพื่อ @AleksiTorhamo สำหรับตระหนักว่าเราก็สามารถกู้คืนเงินต้นระยะเวลาจากดัชนีของเกิดขึ้นครั้งแรกของsใน(s+s)[1:-1]และสำหรับการแจ้งฉันของตัวเลือกstartและการขัดแย้งของงูใหญ่endstring.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
มันเหมือนกับว่าคุณเอาบาปหรือคลื่นคอสจากการทำงานเป็นระยะและเลื่อนไปทางขวา เนื่องจากเป็นช่วงเวลาที่สมบูรณ์คลื่นในที่สุดก็จะเข้ากันได้อย่างสมบูรณ์แบบ ... แนวคณิตศาสตร์สำหรับการแก้ปัญหานี้เป็นเพียงปรากฎการณ์ :) ฉันหวังว่าฉันจะให้ + upvotes ของคุณ
Shashank

6
กุยปรับปรุงล่าสุดเพื่อPEP 8มีความเกี่ยวข้องที่นี่: "สอดคล้องในงบการกลับมาไม่ว่าทั้งหมดงบผลตอบแทนในการทำงานควรกลับแสดงออกหรือไม่มีของพวกเขาควร.. ถ้ามีผลตอบแทนที่คำสั่งกลับแสดงออกงบผลตอบแทนใด ๆ ที่มีค่าไม่เป็น ที่ส่งคืนควรระบุสิ่งนี้อย่างชัดเจนว่าไม่มีการส่งคืนและควรมีคำสั่งส่งคืนที่ส่วนท้ายของฟังก์ชัน (ถ้าสามารถเข้าถึงได้) "
Zero Piraeus

8
@WayneConrad ใช้สตริงพูด"abcd", pop "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()กับ regex ^(.+?)\1+$ซึ่งทั้งหมดนั้นมีรสชาติที่เป็นส่วนตัวมากกว่าสิ่งอื่นใด


6
ฉันไม่รู้ว่าทำไมซับสองอันนี้จึงไม่ใช่คำตอบที่ได้รับคะแนนสูงสุด คำตอบอื่น ๆ นั้นไม่เลว แต่อันนี้ดีกว่ามาก (มันอาจใช้นิพจน์ปกติที่ลบล้างบ่อย ๆแต่ฉันสามารถตรวจสอบได้ง่ายกว่าคำตอบที่ยาวกว่านี้ซึ่งเต็มไปด้วยบล็อกซ้อนกันการพิมพ์ผิดที่อาจเกิดขึ้นข้อผิดพลาดที่ละหนึ่ง ฯลฯ ) อืมแย่กว่าดีกว่า ฉันคิดว่า
พอลเดรเปอร์

9
ฉันคิดว่ามีสองเหตุผลหลักสำหรับที่: 1) โปรแกรมเมอร์บางคนเช่นคณิตศาสตร์มากกว่าที่พวกเขาชอบ regexes และ 2) เนื่องจากความยาวและลักษณะที่แตกต่างกันของสตริงการป้อนข้อมูลทำให้คำตอบที่แตกต่างกันดึงประสิทธิภาพการทำงาน (ซึ่งอาจไม่ปรากฏในข้อมูลจริง) ทำให้โซลูชันนี้ปรากฏขึ้นไม่ดี
TigerhawkT3

บางครั้งคุณพบอคติที่ดีต่อการแสดงออกปกติ ฉันมีผู้จัดการ 2 คนที่ห้ามการใช้งานนิพจน์ทั่วไปเพราะพวกเขาได้ยินว่านิพจน์ทั่วไปเป็นเครื่องมือที่ไม่ถูกต้องสำหรับงาน ยกเว้น offcourse แล้วพวกเขาก็ดำเนินการโดยขอให้ฉันจะใช้เครื่องมือ regexp
joojaa

1
@ PaulDraper: เดาว่า regex กำลังทำอะไรอยู่เบื้องหลัง? มันคือการแยกสตริงและเก็บแต่ละองค์ประกอบจนกว่าจะมีการจับคู่ reapeatition ที่เกิดขึ้น นั่นเป็นสิ่งที่ statet OP ช้าเกินไป ดังนั้นเพียงเพราะมันเป็นสายการบิน 2 เส้นจึงไม่มีการแสดงใด ๆ ชนะ
dhein

2
@Zaibis ปกติแล้วฉันเห็นด้วย แต่นี่เป็นวิธีแก้ปัญหาที่สั้นและเร็วที่สุด ( stackoverflow.com/a/29482936/1212596 )) ยกเว้นของ David ซึ่งโพสต์หลังจากที่ฉันแสดงความคิดเห็นแล้ว ฉันชอบแนวทางของเดวิดมากกว่า (ฉลาด!)
พอลเดรเปอร์

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

แก้ไข:ใน Python 3 /โอเปอเรเตอร์มีการเปลี่ยนแปลงเพื่อทำการหารแบบลอยตามค่าเริ่มต้น ในการรับการintแบ่งจาก Python 2 คุณสามารถใช้//โอเปอเรเตอร์แทนได้ ขอบคุณ @ TigerhawkT3 ที่นำสิ่งนี้มาสู่ความสนใจของฉัน

//ผู้ประกอบการดำเนินการในส่วนจำนวนเต็มทั้งงูหลาม 2 และ Python 3 ดังนั้นฉันได้ปรับปรุงคำตอบที่จะสนับสนุนทั้งสองรุ่น ส่วนที่เราทดสอบเพื่อดูว่าสตริงย่อยทั้งหมดเท่ากันคือตอนนี้เป็นการดำเนินการลัดวงจรโดยใช้allและนิพจน์ตัวสร้าง

UPDATE:ในการตอบสนองต่อการเปลี่ยนแปลงในคำถามเดิมตอนนี้รหัสได้รับการปรับปรุงเพื่อส่งกลับสตริงย่อยการทำซ้ำที่เล็กที่สุดถ้ามันมีอยู่และNoneถ้ามันไม่ได้ @godlygeek ได้แนะนำให้ใช้divmodเพื่อลดจำนวนการวนซ้ำในตัวdivisorsสร้างและรหัสได้รับการปรับปรุงให้ตรงกับที่ ตอนนี้มันกลับตัวหารบวกทั้งหมดnในลำดับพิเศษของnตัวเอง

การปรับปรุงเพิ่มเติมสำหรับประสิทธิภาพสูง:หลังจากการทดสอบหลายครั้งฉันได้ข้อสรุปว่าการทดสอบความเท่าเทียมกันของสตริงมีประสิทธิภาพที่ดีที่สุดจากการแบ่งส่วนหรือโซลูชันตัววนซ้ำใน Python ดังนั้นฉันจึงนำหนังสือเล่มนี้ออกจาก @ TigerhawkT3 และอัปเดตโซลูชันของฉัน ตอนนี้เร็วกว่า 6x เร็วเท่าที่สังเกตได้เร็วกว่าสารละลายของ Tigerhawk แต่ช้ากว่า David


3
วิธีนี้น่าทึ่งมาก คุณสามารถเปลี่ยนวิธีตัวหารให้เป็นไปตามรูปแบบการผลิตและผู้บริโภค เพื่อให้ได้ผลลัพธ์ตามที่พบ น่าเสียดายถ้านี่ไม่ใช่คำตอบที่สูงสุด ทุกอย่างอื่นคือกำลังดุร้าย
JustinDanielson

3
@JustinDanielson มันจะส่งคืนวัตถุกำเนิดที่สร้างขึ้นจากการแสดงออกของเครื่องกำเนิดซึ่งเป็นผู้ผลิตโดยนัย :) มันจะขี้เกียจประเมินตัวหาร
Shashank

1
โอ๋ ฉันไม่รู้ ดียิ่งขึ้นแล้ว : DI เข้าใจสาเหตุที่คุณต้องการหลีกเลี่ยง sqrt แต่คุณสามารถใช้ n / 2 เป็นขอบเขตสูงสุดสำหรับช่วงตัวหาร
JustinDanielson

1
@JustinDanielson ขอขอบคุณสำหรับข้อเสนอแนะตอนนี้ขอบเขต(n/2)รวมอยู่ในขณะนี้
Shashank

1
ควรn / 2ในการdivisors()เป็นn // 2?
TigerhawkT3

87

ต่อไปนี้เป็นเกณฑ์มาตรฐานสำหรับคำตอบที่หลากหลายสำหรับคำถามนี้ มีผลลัพธ์ที่น่าแปลกใจบางอย่างรวมถึงประสิทธิภาพที่แตกต่างกันขึ้นอยู่กับสตริงที่จะทดสอบ

ฟังก์ชั่นบางอย่างได้รับการแก้ไขให้ทำงานกับ Python 3 (ส่วนใหญ่โดยการแทนที่/ด้วย//เพื่อให้แน่ใจว่าการหารจำนวนเต็ม) ถ้าคุณเห็นบางสิ่งบางอย่างที่ไม่ถูกต้องต้องการที่จะเพิ่มฟังก์ชั่นของคุณหรือต้องการเพิ่มสตริงทดสอบอีก @ZeroPiraeus ping ในเนตหลาม

โดยสรุป: มีความแตกต่างประมาณ 50 เท่าระหว่างโซลูชันที่ดีที่สุดและแย่ที่สุดสำหรับชุดข้อมูลขนาดใหญ่ที่จัดทำโดย OP ที่นี่ (ผ่านความคิดเห็นนี้ ) โซลูชันของ David Zhangเป็นผู้ชนะที่ชัดเจนโดยมีประสิทธิภาพเหนือกว่าผู้อื่นทั้งหมดประมาณ 5x สำหรับชุดตัวอย่างขนาดใหญ่

คำตอบสองสามข้อนั้นช้ามากในกรณี "ไม่มีคู่" ที่มีขนาดใหญ่มาก มิฉะนั้นฟังก์ชั่นที่ดูเหมือนจะเท่ากันหรือผู้ชนะที่ชัดเจนขึ้นอยู่กับการทดสอบ

นี่คือผลลัพธ์รวมถึงแปลงที่ทำโดยใช้ matplotlib และ seaborn เพื่อแสดงการแจกแจงที่แตกต่างกัน:


Corpus 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 กราฟ


Corpus 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 กราฟ


Corpus 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

โซลูชันที่ไม่ใช่ 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]

แก้ปัญหาที่ไม่ใช่ regex เร็วขึ้นขอบคุณ @ThatWeirdo (ดูความคิดเห็น):

def repeat(string):
    l = len(string)
    for i in range(1, len(string)//2+1):
        if l%i: continue
        s = string[0:i]
        if s*(l//i) == string:
            return s

วิธีแก้ปัญหาข้างต้นนั้นช้ากว่าเดิมเล็กน้อยมากโดยทั่วไปไม่กี่เปอร์เซ็นต์ แต่โดยทั่วไปแล้วมันจะเร็วกว่านิดหน่อย แต่บางครั้งก็เร็วกว่ามาก มันยังไม่เร็วกว่าของ davidism สำหรับสายยาวและโซลูชัน regex ของ zero นั้นยอดเยี่ยมสำหรับสายสั้น ๆ มันออกมาเร็วที่สุด (ตามการทดสอบของ davidism ใน github - ดูคำตอบของเขา) ด้วยสายอักขระที่มีประมาณ 1,000-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 วินาทีสำหรับสตริงยาว (1,000 ตัวอักษร) ที่ประสบความสำเร็จและบิตภายใต้มิลลิวินาทีเล็กน้อยสำหรับสตริงยาวที่ไม่สำเร็จ (กรณีที่แย่ที่สุด) . ดังนั้นสตริง 1,000 พันตัวจะใช้เวลาประมาณหนึ่งวินาที เมื่อเปรียบเทียบกับคำตอบทางคณิตศาสตร์แล้วพบว่าการจับคู่เร็วกว่า 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

ส่งคืนการจับคู่ที่สั้นที่สุดหรือไม่มีหากไม่มีคู่ที่ตรงกัน


16

ปัญหานี้อาจแก้ไขได้ในO(n)กรณีที่แย่ที่สุดด้วยฟังก์ชั่นคำนำหน้า

หมายเหตุมันอาจจะช้าลงในกรณีทั่วไป (UPD และช้ามาก) กว่าโซลูชั่นอื่น ๆ ที่ขึ้นอยู่กับจำนวนของตัวหารของnแต่มักจะพบว่าล้มเหลวไม่ช้าก็เร็วผมคิดว่าหนึ่งในกรณีที่เลวร้ายสำหรับพวกเขาที่จะเป็นaaa....aabที่มีn - 1 = 2 * 3 * 5 * 7 ... *p_n - 1 aของ

ก่อนอื่นคุณต้องคำนวณฟังก์ชั่นคำนำหน้า

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()ไม่ได้เป็นงูหลามที่ถูกต้อง: คุณมีทวิภาคหายไปของคุณwhileและifงบและแทน&& 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

ขอบคุณ TigerhawkT3 สำหรับการสังเกตว่าlength // 2ไม่+ 1ตรงกับตัวababเคส


วิธีการแก้ปัญหานี้เหมือนกันกับโซลูชั่นที่ได้รับการปรับปรุงของฉัน ฉันเห็นว่าคุณมีrangeขีด จำกัดlength//2เหมือนที่ฉันทำ - คุณต้องเปลี่ยนเป็นlength//2+1ถ้าคุณต้องการจับสายที่สองเท่า (เช่น'aabaab')
TigerhawkT3

และตอนนี้พวกเขาเหมือนกัน! \ o / ฉันจำเป็นต้องให้ความสำคัญกับการเพิ่มประสิทธิภาพในอนาคต แต่ฉันดีใจที่อัลกอริทึมของตัวเองดี
TigerhawkT3

15

นี่คือวิธีแก้ปัญหาตรงไปตรงมาโดยไม่ต้อง regexes

สำหรับสตริงย่อยของการsเริ่มต้นจากดัชนี zeroth ที่มีความยาว 1 ถึงlen(s)ให้ตรวจสอบว่าสตริงย่อยนั้นsubstrเป็นรูปแบบซ้ำ ๆ หรือไม่ การตรวจสอบนี้สามารถทำได้โดยการเชื่อมโยงsubstrกับตัวเองครั้งดังกล่าวว่าความยาวของสตริงที่เกิดขึ้นจึงมีค่าเท่ากับความยาวของratio ด้วยเหตุนี้sratio=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

ฉันเริ่มต้นด้วยการแก้ไขปัญหานี้มากกว่าแปดวิธี บางฐานใน regex (จับคู่ findall แยก) บางส่วนของการแบ่งสตริงและการทดสอบและบางส่วนที่มีวิธีการสตริง (ค้นหานับแยก) แต่ละคนมีประโยชน์ในความชัดเจนของรหัสขนาดรหัสความเร็วและการใช้หน่วยความจำ ฉันจะโพสต์คำตอบของฉันที่นี่เมื่อฉันสังเกตเห็นว่าความเร็วการดำเนินการอยู่ในอันดับที่สำคัญดังนั้นฉันจึงทำการทดสอบและปรับปรุงเพิ่มเติมเพื่อให้ได้สิ่งนี้:

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 ที่มีตัวดำเนินการคูณและการเปรียบเทียบสตริงนั้นได้รับการปรับให้เหมาะสมที่สุด


ไม่เลวเลย :-)มาตรฐานทำงานบน Python 3.4 (ส่วนหนึ่งเป็นเพราะ OP ไม่ได้ระบุรุ่นและนั่นคือสิ่งที่ทุกคนควรใช้และอีกส่วนหนึ่งเพราะใช้statisticsโมดูลใหม่) ดังนั้นฉันต้องเปลี่ยน/s เป็น//s และแทนที่xrange()ด้วยrange()(ซึ่งทำงานเช่น 2.x's xrange()ใน 3.x)
Zero Piraeus

นี่คือการแก้ไขมาตรฐานเพื่อให้คุณสามารถตรวจสอบการเปลี่ยนแปลงของฉันได้
Zero Piraeus

ขอบคุณเป็นศูนย์ นั่นเร็วมาก. ผลลัพธ์ลดลงเล็กน้อยจากการคาดการณ์ของฉัน ฉันสงสัยว่าเทคนิคที่ใช้สำหรับความเร็วใน Python 2.7 นั้นไม่มีประสิทธิภาพใน Python 3.4 โอ้ - การออกกำลังกายเพื่อการศึกษาและความสนุกสนาน
Logic Knight อัศวิน

//ใน 3.x คือการหารจำนวนเต็ม (เช่นเดียวกับพฤติกรรม 2.x ของ/) ในขณะที่ 3.x's /คือการหารลอย (ซึ่งฉันแน่ใจว่าจะช้ากว่ามากแม้ว่ามันจะไม่ได้แก้ปัญหาของคุณโดยทำให้เกิดความพยายามที่จะใช้ ลอยเป็นดัชนี) ดังที่ได้กล่าวไว้ 3.x's range()นั้นเหมือนกับ 2.x's xrange(); ไม่เท่ากับ 2.x range()มีอยู่ใน 3.x ดังนั้นฉันไม่คิดว่าเป็นสาเหตุของความแตกต่างระหว่างการวัดประสิทธิภาพและเวลาที่คุณทำ อาจเป็นเพียงว่า 3.x นั้นช้ากว่า 2.x (หรือเครื่องของคุณอาจเร็วกว่าของฉัน)
Zero Piraeus

เมื่อฉันได้รับเวลาฉันจะได้ดูความแตกต่างของเวลาทำงานระหว่าง Python 2 และ Python 3 อย่างใกล้ชิด
Logic Knight

2

ฟังก์ชั่นนี้ทำงานเร็วมาก (ทดสอบแล้วและเร็วกว่าโซลูชันที่เร็วที่สุด 3 เท่าบนสตริงที่มีตัวอักษรมากกว่า 100k และความแตกต่างก็จะยิ่งใหญ่ตามรูปแบบการทำซ้ำที่ยาวกว่า) พยายามลดจำนวนการเปรียบเทียบที่จำเป็นเพื่อให้ได้คำตอบ:

def repeats(string):
    n = len(string)
    tried = set([])
    best = None
    nums = [i for i in  xrange(2, int(n**0.5) + 1) if n % i == 0]
    nums = [n/i for i in nums if n/i!=i] + list(reversed(nums)) + [1]
    for s in nums:
        if all(t%s for t in tried):
            print 'Trying repeating string of length:', s
            if string[:s]*(n/s)==string:
                best = s
            else:
                tried.add(s)
    if best:
        return string[:best]

โปรดทราบว่าสำหรับสตริงที่มีความยาว 8 จะตรวจสอบเฉพาะส่วนของขนาด 4 และไม่จำเป็นต้องทดสอบเพิ่มเติมเนื่องจากรูปแบบของความยาว 1 หรือ 2 จะส่งผลให้เกิดรูปแบบความยาวซ้ำ 4:

>>> repeats('12345678')
Trying repeating string of length: 4
None

# for this one we need only 2 checks 
>>> repeats('1234567812345678')
Trying repeating string of length: 8
Trying repeating string of length: 4
'12345678'

ขอบคุณ :) ฉันไม่ได้ปรับให้เหมาะสมมากนัก ฉันแค่ต้องการนำเสนอวิธีการที่แตกต่างกันเพราะคำตอบอื่น ๆ มุ่งเน้นไปที่การค้นหารูปแบบและวิธีการของฉันมุ่งเน้นไปที่การพิสูจน์ว่าไม่มีรูปแบบ :) ดังนั้นสำหรับสตริงแบบสุ่มอัลกอริทึมของฉันควรจะทำงานเร็วขึ้นมาก
Piotr Dabkowski

0

ในคำตอบของเดวิดจางถ้าเรามีการจัดเรียงของกันชนกลมบางนี้จะไม่ทำงาน: principal_period('6210045662100456621004566210045662100456621')เนื่องจากการเริ่มต้นที่ผมจะชอบมันจะคายออก:62100456621

การขยายโซลูชันของเขาเราสามารถใช้สิ่งต่อไปนี้:

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.