Bir dizenin kendisini Python'da tekrarlayıp tekrarlamadığını nasıl anlayabilirim?


352

Belirli bir dize tüm dize için kendini tekrarlar olup olmadığını test etmek için bir yol arıyorum.

Örnekler:

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

kendilerini tekrarlayan dizelerdir ve

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

olmayanlara örnektir.

Verdiğim dizelerin yinelenen bölümleri oldukça uzun olabilir ve dizelerin kendileri 500 veya daha fazla karakter olabilir, bu yüzden bir desen oluşturmaya çalışan her karakter arasında döngü yapıp, dizenin geri kalanına karşı deseni kontrol etmek çok yavaş görünüyor. Bunu potansiyel olarak yüzlerce dizeyle çarpın ve sezgisel bir çözüm göremiyorum.

Biraz regexes içine baktım ve ne aradığınızı veya en azından aradığınız desenin uzunluğunu bildiğinizde iyi görünüyorlar. Ne yazık ki, hiçbirini bilmiyorum.

Bir dizenin kendisini tekrar edip etmediğini ve öyleyse, en kısa tekrar eden alt dizinin ne olduğunu nasıl anlayabilirim?


15
Bir desen oluşturmaya çalışan her karakter arasında döngü yapmak, sonra dizenin geri kalanına karşı deseni kontrol etmek çok yavaş görünüyor - ama öyle mi?
Tim


2
@AvinashRaj Bu bir dizenin sadece bir kısmı ile eşleşiyor, tam olarak değil.
John

11
@AvinashRaj OP olası tüm çözümleri soruyor. Bağlantı verdiğiniz soru yalnızca normal ifade çözümünü kabul eder . Normal ifadenin sorunu çözebileceğini, ancak gerektiğinden çok daha fazla zaman içinde olabileceğini unutmayın . Örneğin, optimal bir çözüm (yani doğrusal zaman) metnin sonek ağacını kullanır. Sadece en uzun tekrarlanan alt dizeyi bulmanız ve uzunlukları kontrol etmeniz yeterlidir.
Bakuriu

2
@ TigerhawkT3 Gerçek veri kümesi çok büyük ve hantal, ancak sorudaki örnekler bunun bir parçası ve eğer isterseniz, burada biraz daha var .
John

Yanıtlar:


570

İşte düzenli ifadeleri ve yavaş Python döngülerini önleyen özlü bir çözüm:

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

Karşılaştırma sonuçları için @davidism tarafından başlatılan Topluluk Wiki yanıtına bakın . Özetle,

David Zhang'ın çözümü, büyük örnek seti için diğerlerinden en az 5 kat daha iyi performans gösteren açık kazanandır.

(Bu cevabın sözleri benim değil.)

Bu, bir ipin yalnızca kendisinin önemsiz olmayan bir rotasyonuna eşit olması durumunda periyodik olduğu gözlemine dayanır. O zaman ilk geçtiği dizinden başlıca dönemi kurtarabilirsiniz fark için @AleksiTorhamo şeref sin (s+s)[1:-1]ve opsiyonel beni bilgilendirdiğiniz için startve endPython en argümanları string.find.


19
Örneğin, .find()veya .index()yerine kullanarak en kısa yinelenen alt diziyi bulmak için bunu genişletebilirsiniz in. (s+s).find(s, 1, -1).
Aleksi Torhamo

11
Ayrıca, en azından daha büyük dizeler için (s+s).find(s, 1, -1)(çok az) daha hızlı olacağını düşünüyorum (s+s)[1:-1].find(s), çünkü dilimleme (neredeyse) tüm dize başka bir kopyasını oluşturmak zorunda anlamına gelir.
Aleksi Torhamo

13
Periyodik bir fonksiyondan bir günah veya cos dalgası alıp sağa kaydırırsanız olduğu gibi. Tamamen periyodik olduğundan, dalgalar sonunda mükemmel bir şekilde eşleşecektir ... Bu çözümün matematik paralellikleri sadece olağanüstü. :) Keşke sana + ∞ hediye verebilseydim.
Shashank

6
Guido'nun son güncelleme için PEP 8 burada alakalı şudur: "dönüş tablolara tutarlı olun Ya bir fonksiyonu tüm dönüş ifadeleri bir ifade dönmelidir veya bunların hiçbiri gerekir.. Herhangi Dönüş ifadesi dönerse bir ifade, hiçbir değer herhangi dönüş ifadeleri nerede return, bunu return Yok olarak açıkça belirtmelidir ve işlevin sonunda (ulaşılabilirse) açık bir return ifadesi bulunmalıdır . "
Sıfır Pire

8
@WayneConrad Bir dize alın, diyelim ki, "abcd"sağdaki karakteri çıkarın ve elde etmek için tekrar sola yapıştırın "dabc". Bu işleme bir dizgiyi sağa 1 karakter döndürme denir . nBir dizeyi nkarakterlerle sağa döndürmek için süreleri tekrarlayın . Şimdi, bir kkarakter dizimiz varsa, dizenin herhangi bir katından sağa dönen kdizgenin değişmeden kaldığını gözlemleyin . Bir dizenin önemsiz bir dönüşü, karakter numarası dizenin uzunluğunun katı olmayan bir dizidir.
David Zhang

181

İşte düzenli ifadeler kullanan bir çözüm.

import re

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

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

Sorudaki örnekler üzerinde tekrar etmek:

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)

... bu çıktıyı üretir:

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

Normal ifade (.+?)\1+$üç bölüme ayrılır:

  1. (.+?)herhangi bir karakterin en az birini (ancak mümkün olduğunca az) içeren ( +?açgözlü olmadığı için ) eşleşen bir gruptur .

  2. \1+ ilk bölümde eşleşen grubun en az bir tekrarını kontrol eder.

  3. $yinelenen alt dizelerden sonra fazladan yinelenmeyen bir içerik olmadığından emin olmak için dizenin sonunu denetler (ve kullanılması , yinelenen alt dizelerden öncere.match() yinelenmeyen bir metin olmamasını sağlar ).

Python 3.4 ve sonrasında, onun yerine bırakabilir $ve kullanabilirsiniz re.fullmatch()veya (en azından 2.3'e kadar olan herhangi bir Python'da) diğer yöne gidip, her şeyden çok kişisel zevkinize bağlı olan re.search()normal ifadeyle birlikte kullanabilirsiniz ^(.+?)\1+$.


6
Bu özlü iki astarın neden en yüksek oy alan cevap olmadığı hakkında hiçbir fikrim yok. Diğer cevaplar kötü değil, ama bu çok daha iyi. (Sık sık bozulmuş düzenli ifadeyi kullanabilir , ancak bunu iç içe bloklar, potansiyel yazım hataları, tek tek hatalar, vb. İle dolu diğer daha uzun cevaplardan çok daha kolay bir şekilde inceleyebilirim. Sanırım.
Paul Draper

9
Bunun iki ana nedeni olduğunu düşünüyorum: 1) regexes gibi daha matematik gibi bazı programcılar ve 2) giriş dizeleri uzunluğunu ve doğasını değiştirmek farklı cevaplar performans, süper uzun kenar durumda dizeleri ileriye götürür (gerçek verilerde bile görünmeyebilir) bu çözümün yetersiz görünmesini sağlar.
TigerhawkT3

bazen düzenli ifadelere karşı büyük bir önyargı ile karşılaşırsınız. Ive'nin düzenli ifadelerin kullanımını yasaklayan 2 yöneticisi vardı çünkü düzenli ifadelerin iş için yanlış araç olduğunu duymuşlardı.
Offcourse

1
@PaulDraper: Tahmin et sahnenin arkasında ne yapıyor? dizeyi ayrıştırır ve her öğeyi bir yeniden toplama eşleşmesi gerçekleşene kadar saklar. Bu aynı OP statet çok yavaş. bu yüzden 2 astar olduğu için hiçbir performans kazancı yoktur.
dhein

2
@Zaibis, normalde aynı fikirdeyim, ama bu hem en kısa hem de en hızlı çözüm ( stackoverflow.com/a/29482936/1212596)... David'in dışında, bu yorumu yaptıktan sonra gönderildi. Aslında David'in yaklaşımını daha çok seviyorum (zeki!).
Paul Draper

90

Bir dizenin tekrarlandığı düşünülmek için, uzunluğunun tekrarlanan dizisinin uzunluğuna bölünebilmesi gerektiği gözlemini yapabilirsiniz. Buradan uzunlukta bölenler üreten bir çözümdür, göz önüne alındığında 1için n / 2kapsayıcı, bölenler uzunluğunda alt dizelere içine orijinal dize böler ve sonuç kümesinin eşitliğini test eder:

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

DÜZENLEME: Python 3'te, /operatör varsayılan olarak şamandıra bölümü yapacak şekilde değişmiştir. intPython 2'den bölüm almak için //bunun yerine operatörü kullanabilirsiniz . @ TigerhawkT3'e bunu dikkatimi çektiği için teşekkür ederim.

//Her iki Python 2 ve Python 3'te bölünme tamsayı operatörü gerçekleştirir, bu yüzden her iki sürümü de desteklemek cevabını güncelledik. Tüm alt dizelerin eşit olup olmadığını test ettiğimiz bölüm artık kısa devre işlemi allve bir jeneratör ifadesi.

GÜNCELLEME: Orijinal sorudaki bir değişikliğe yanıt olarak, kod şimdi varsa ve Noneyoksa en küçük yinelenen alt dizeyi döndürmek üzere güncelleştirilmiştir . @godlygeek, jeneratördeki divmodyineleme sayısını azaltmak için kullanılmasını önerdi divisorsve kod da buna uyacak şekilde güncellendi. Şimdi n, nkendisinin dışında artan tüm düzenleyicileri artan düzende döndürüyor .

Yüksek performans için daha fazla güncelleme: Birden fazla testten sonra, dize eşitliğini test etmenin Python'daki herhangi bir dilimleme veya yineleyici çözümünden en iyi performansa sahip olduğu sonucuna vardım. Böylece @ TigerhawkT3'ün kitabından bir yaprak çıkardım ve çözümümü güncelledim. Şimdi eskisinden 6 kat daha hızlı, Tigerhawk'ın çözümünden fark edilir derecede daha hızlı ama David'inkinden daha yavaş.


3
Bu çözüm şaşırtıcı. Ürün-tüketici modelini takip etmek için bölücüler yöntemini değiştirebilirsiniz. Böylece buldukça sonuçlar verir. Bu en yüksek cevap değilse utanç verici olacak. Diğer her şey kaba kuvvettir.
JustinDanielson

3
@JustinDanielson Örtük bir üretici olan bir jeneratör ifadesinden oluşturulan bir jeneratör nesnesini döndürür :) Bölenleri tembel olarak değerlendirir.
Shashank

1
Ohh. Bunu bilmiyordum. O zaman bile daha iyi. : DI neden sqrt önlemek istiyorum anlamak, ama n / 2 bölen aralığı için üst sınır olarak kullanabilirsiniz.
JustinDanielson

1
@JustinDanielson Öneri için teşekkürler, üst sınır aralığı şimdi (n/2)dahil.
Shashank

1
Meli n / 2içinde divisors()olmak n // 2?
TigerhawkT3

87

İşte bu sorunun çeşitli cevapları için bazı kriterler. Test edilen dizeye bağlı olarak çılgınca farklı performanslar da dahil olmak üzere bazı şaşırtıcı sonuçlar vardı.

Bazı fonksiyonlar (özellikle değiştirerek Python 3 ile çalışmak üzere modifiye edilmiştir /ile //tamsayı bölme temin etmek üzere). Yanlış bir şey görürseniz, işlevinizi eklemek veya başka bir test dizesi eklemek istiyorsanız, Python sohbet odasında @ZeroPiraeus'a ping atın .

Özetle: OP tarafından burada sağlanan büyük örnek veri seti için en iyi ve en kötü performans gösteren çözümler arasında yaklaşık 50 kat fark vardır ( bu yorum aracılığıyla ). David Zhang'ın çözümü , büyük örnek seti için diğerlerinden 5 kat daha iyi performans gösteren açık kazanandır.

Birkaç cevap çok derece büyük "eşleşmeyen" durumlarda yavaştır. Aksi takdirde, teste bağlı olarak işlevler eşit olarak eşleşir veya açık kazananlar gibi görünür.

Farklı dağılımları göstermek için matplotlib ve deniz dibinin kullanıldığı araziler de dahil olmak üzere sonuçlar:


Corpus 1 (birlikte verilen örnekler - küçük set)

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

Corpus 1 grafiği


Corpus 2 (birlikte verilen örnekler - büyük set)

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

Corpus 1 grafiği


Corpus 3 (kenar kasaları)

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

Corpus 3 grafiği


Testler ve ham sonuçlar burada mevcuttur .


37

Normal olmayan çözüm:

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 sayesinde daha hızlı regex olmayan çözüm (bkz. Yorumlar):

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

Yukarıdaki çözüm orijinalinden yüzde birkaç daha nadirdir, ancak genellikle biraz daha hızlıdır - bazen çok daha hızlıdır. Hala uzun dizeler için davidizminkinden daha hızlı değildir ve sıfırın normal ifade çözümü kısa dizeler için üstündür. Yaklaşık 1000-1500 karakterlik dizelerle en hızlı (davidism'in github testine göre - cevabına bakın) ortaya çıkıyor. Ne olursa olsun, test ettiğim tüm durumlarda güvenilir bir şekilde en hızlı ikinci (veya daha iyi). Teşekkürler, ThatWeirdo.

Ölçek:

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

Sonuçlar:

009
2547
abcde
None
None
None

Bu kaba kuvvet çözümü değil mi?
JustinDanielson

7
@JustinDanielson Evet. Ancak bir çözüm daha az değildir.
Sinkingpoint

3
Kısa dizeler için yaklaşık 1e-5 ila 3e-5 saniye, başarılı uzun (1000 karakter) dizeler için 3e-5 ila 4e-5 saniye ve başarısız uzun dizeler için bir milisaniyenin biraz altında (en kötü durum) . Yani 1000 karakterlik dizeler bir saniye sürecekti. Matematik cevabı ile karşılaştırıldığında, bu maçları 10 kat daha hızlı bulur, ancak başarısız olması 3 kat daha uzun sürer.
TigerhawkT3

repeat('aa')dönerNone
Tom Cornebize

2
len(string[0:i])her zaman eşittir i(en azından bu durumda). Bunların değiştirilmesi len(string)ve string[0:i]değişkenlerin kaydedilmesi de işleri hızlandırabilir. Ayrıca IMO bu harika bir çözüm, harika;)
ThatWeirdo

24

İlk olarak, dizeyi "2 parçalı" bir kopya olduğu sürece yarıya indirin. Bu, çift sayıda tekrar olması durumunda arama alanını azaltır. Ardından, en küçük yinelenen dizeyi bulmak için ileri doğru çalışarak, gittikçe daha büyük alt dizeyle tam dizenin bölünüp bölünmediğini yalnızca boş değerlere neden olup olmadığını denetleyin. Sadece alt dizelerin length // 2test edilmesi gerekir, çünkü üzerinde herhangi bir şey tekrarlanmayacaktır.

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

Bu en kısa eşleşmeyi veya eşleşme yoksa Yok'u döndürür.


16

Sorun ayrıca O(n)önek fonksiyonu ile en kötü durumda çözülebilir .

Not, genel durumda daha yavaş olabilir (UPD: ve çok daha yavaştır), bölücülerin sayısına bağlı olan n, ancak genellikle daha erken başarısız olan diğer çözümlerden daha fazla olabilir , bence onlar için kötü durumlardan biri aaa....aab, nerede olacakn - 1 = 2 * 3 * 5 * 7 ... *p_n - 1 a s'

Öncelikle önek işlevini hesaplamanız gerekir

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

ya cevap yok ya da en kısa süre

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

ve sadece k != n and n % k == 0(o k != n and n % k == 0zaman cevap ise s[:k], başka cevap yoksa

Kanıtı burada kontrol edebilirsiniz (Rusça, ancak çevrimiçi çevirmen muhtemelen hile yapacak)

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

Sizin prefix_function()geçerli Python değil: whileve ififadelerinizde ve &&yerine eksik iki nokta işareti var and. Bunları düzelttikten sonra UnboundLocalError: local variable 'i' referenced before assignment, hat yüzünden başarısız olur for i in range(i, n):.
Sıfır Piraeus

Teşekkürler :-) Eğer prefix_function()diğer cevaplara benzer sonuçları döndürmek için kullanan bir işlevi bir araya koyabilirsiniz - ya en kısa alt dize veya None- Ben bir araya koymak revize bir kıyaslama içine dahil edeceğiz.
Sıfır Pire

@ZeroPiraeus, Aslında iyi çalışıyor, sadece yanlış bir şekilde
çağırdım

16

Bu sürüm yalnızca dize uzunluğunun faktörleri olan aday dizi uzunluklarını dener; ve *aday dizisinden tam uzunlukta bir dize oluşturmak için operatörü kullanır :

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'e, length // 2olmadan davanın + 1eşleşmesinin başarısız olacağını fark ettiği için teşekkürler abab.


Bu çözüm, optimize edilmiş çözümümle neredeyse aynı. Tıpkı benim yaptığım gibi bir rangesınırınız olduğunu görüyorum length//2- length//2+1eğer tam olarak iki katına (örneğin 'aabaab') iki dize yakalamak istiyorsanız bunu değiştirmek zorundasınız .
TigerhawkT3

Ve şimdi aynılar! \ o / Gelecekte optimizasyona daha fazla dikkat etmem gerekiyor, ancak algoritmanın kendisinin sağlam olduğuna sevindim.
TigerhawkT3

15

İşte regexes olmadan basit bir çözüm.

Oluşan altdizgelerin için syoluyla uzunlukları 1, sıfırıncı indeksi itibaren len(s)bu substring, eğer kontrol substrtekrar şablonudur. Bu kontrol , bu şekilde oluşturulan ipin uzunluğunun uzunluğuna eşit olacak şekilde substrkendi ratiozamanları ile birleştirilerek gerçekleştirilebilir s. Dolayısıyla ratio=len(s)/len(substr).

Bu tür bir alt dize bulunduğunda geri dönün. Bu, eğer varsa, mümkün olan en küçük alt dizeyi sağlayacaktır.

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"

Şimdi buna dikkatlice baktığım için, orijinal olarak yayınlanan (herhangi bir düzenlemeden önce) çözümümle neredeyse aynı görünüyor, tek fark uzunluk ve alt dize tasarrufu. Sanırım oldukça iyi bir algoritmam vardı. : P
TigerhawkT3

@ TigerhawkT3 Evet! :)
Saksham Varma

9

Bu soruna sekizden fazla çözümle başladım. Bazıları regex (kibrit, findall, split), bazıları string dilimleme ve test bazında, bazıları string metotları (find, count, split) idi. Her birinin kod netliği, kod boyutu, hız ve bellek tüketiminde faydaları vardı. Yürütme hızının önemli olarak derecelendirildiğini fark ettiğimde cevabımı buraya gönderecektim, bu yüzden buna ulaşmak için daha fazla test ve iyileştirme yaptım:

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]

Bu cevap burada diğer birkaç cevaba benziyor, ancak başkalarının kullanmadığı birkaç hız optimizasyonu var:

  • xrange bu uygulamada biraz daha hızlı,
  • giriş dizesi tek bir uzunluktaysa, çift uzunluklu alt dizeleri kontrol etmeyin,
  • s[:n]doğrudan kullanarak , her döngüde bir değişken oluşturmaktan kaçınırız.

Bunun ortak donanım ile yapılan standart testlerde nasıl performans gösterdiğini görmek isterim. Çoğu testte David Zhang'ın mükemmel algoritmasının çok kısa olacağına inanıyorum, ancak aksi takdirde oldukça hızlı olmalı.

Bu sorunun çok sezgisel olduğunu gördüm. Hızlı olacağını düşündüğüm çözümler yavaştı. Yavaş görünen çözümler hızlıydı! Python'un çarpma işleci ve dize karşılaştırmaları ile dize oluşturma işlemi oldukça optimize edilmiştir.


Tüm :-) de fena değil Python 3.4 üzerinde kriter çalışır (OP bir versiyonunu belirtmeyen kısmen ve bu yıllardan herkesin ne olmalı yeni kullanır ve kısmen kullanıyor olması statisticssenin değiştirmek zorunda modülü), bu yüzden /hiç s //s ve değiştirme xrange()ile range()(ki davranacağını gibi 2.x en xrange()3.x).
Sıfır Pire

Kıyaslamadaki revizyonlar burada , bu nedenle değişikliklerimi bu arada inceleyebilirsiniz.
Sıfır Pire

Teşekkürler Sıfır. Bu kadar hızlı oldu. Tahminlerime göre sonuçlar biraz düştü. Python 2.7'de hız için kullandığım tekniklerin Python 3.4'te çok etkili olmadığını düşünüyorum. Ah, eğlenceli ve eğitici bir egzersiz.
Mantık Şövalyesi

//3.x tamsayı bölümü (2.x davranışı gibi /), 3.x's /şamandıra bölümü (eminim kullanmak için bir girişim neden olarak çözümünü kırmasa bile çok daha yavaş olurdu endeks olarak bir şamandıra). Belirtildiği gibi, 3.x'ler 2.x'lerle range()aynı şeydir xrange(); range()3.x'te 2.x eşdeğeri yoktur . Bu yüzden karşılaştırmalı değerlendirme ile yaptığınız zamanlamalar arasında herhangi bir tutarsızlığın sebebi olduğunu düşünmüyorum. Muhtemelen 3.x genel olarak 2.x'ten daha yavaştır (veya belki makineniz benimkinden daha hızlıdır).
Sıfır Pire

Biraz zaman aldığımda, Python 2 ve Python 3 arasındaki çalışma zamanı farklılıklarına yakından bakacağım.
Mantık Şövalyesi

2

Bu işlev çok hızlı çalışır (test edilmiştir ve 100k karakterden fazla karakter içeren dizelerde en hızlı çözümden 3 kat daha hızlıdır ve tekrarlanan model ne kadar uzun olursa fark büyür). Cevabı almak için gereken karşılaştırma sayısını en aza indirmeye çalışır:

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]

Örneğin, uzunluk 8 dizesi için sadece boyut 4 parçasını kontrol ettiğini ve daha fazla test etmek zorunda olmadığını unutmayın, çünkü uzunluk 1 veya 2'nin deseni, uzunluk 4'ün tekrarlanan deseniyle sonuçlanacaktır:

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

Teşekkür ederim :) Yine de çok optimize etmedi. Ben sadece farklı bir yaklaşım sunmak istedim çünkü diğer cevaplar kalıbı bulmaya odaklanıyor ve yaklaşımım kalıp olmadığını kanıtlamaya odaklanıyor :) Bu nedenle rastgele dizeler için algoritmam çok daha hızlı çalışmalı.
Piotr Dabkowski

0

: David Zhang'ın cevap olarak bu işi olmaz dairesel tampon çeşit varsa principal_period('6210045662100456621004566210045662100456621')dolayı başlangıç için 621dışarı tükürmek isterdi,: 00456621.

Çözümünü genişletmek için aşağıdakileri kullanabiliriz:

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'da kullanıcı tarafından verilen ana dizede alt dizenin tekrarını kontrol eden kod .

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)

Giriş :

0045662100456621004566210045662100456621

Çıktı :

İpinizin uzunluğu: 40

Alt Dize '00456621' dizede yinelenir '0045662100456621004566210045662100456621'

Giriş :

004608294930875576036866359447

Çıktı :

İpinizin uzunluğu: 30

'004608294930875576036866359447' dizesinde yinelenen Alt Dize bulunamadı

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.