Saya sedang mencari cara untuk menguji apakah suatu string diberikan berulang untuk seluruh string atau tidak.
Contoh:
[
'0045662100456621004566210045662100456621', # '00456621'
'0072992700729927007299270072992700729927', # '00729927'
'001443001443001443001443001443001443001443', # '001443'
'037037037037037037037037037037037037037037037', # '037'
'047619047619047619047619047619047619047619', # '047619'
'002457002457002457002457002457002457002457', # '002457'
'001221001221001221001221001221001221001221', # '001221'
'001230012300123001230012300123001230012300123', # '00123'
'0013947001394700139470013947001394700139470013947', # '0013947'
'001001001001001001001001001001001001001001001001001', # '001'
'001406469760900140646976090014064697609', # '0014064697609'
]
adalah string yang berulang, dan
[
'004608294930875576036866359447',
'00469483568075117370892018779342723',
'004739336492890995260663507109',
'001508295625942684766214177978883861236802413273',
'007518796992481203',
'0071942446043165467625899280575539568345323741',
'0434782608695652173913',
'0344827586206896551724137931',
'002481389578163771712158808933',
'002932551319648093841642228739',
'0035587188612099644128113879',
'003484320557491289198606271777',
'00115074798619102416570771',
]
adalah contoh yang tidak.
Bagian berulang dari string yang saya berikan bisa sangat panjang, dan string itu sendiri bisa 500 atau lebih karakter, jadi perulangan melalui setiap karakter mencoba membangun pola kemudian memeriksa pola vs sisa string tampaknya sangat lambat. Lipat gandakan dengan ratusan string dan saya tidak bisa melihat solusi intuitif.
Saya telah melihat ke regex sedikit dan mereka tampak bagus ketika Anda tahu apa yang Anda cari, atau setidaknya panjang pola yang Anda cari. Sayangnya, saya juga tidak tahu.
Bagaimana saya bisa tahu apakah sebuah string berulang dan jika ya, apa yang merupakan pengulangan terpendek adalah?
Jawaban:
Berikut adalah solusi ringkas yang menghindari ekspresi reguler dan loop in-Python lambat:
Lihat jawaban Wiki Komunitas yang dimulai oleh @davidism untuk hasil benchmark. Singkatnya,
(Kata-kata jawaban itu, bukan milikku.)
Ini didasarkan pada pengamatan bahwa string adalah periodik jika dan hanya jika sama dengan rotasi nontrivial itu sendiri. Kudos to @AleksiTorhamo karena menyadari bahwa kita dapat memulihkan periode pokok dari indeks kejadian pertama
s
in(s+s)[1:-1]
, dan untuk memberi tahu saya tentang opsionalstart
danend
argumen Pythonstring.find
.sumber
.find()
atau.index()
bukannyain
, misalnya.(s+s).find(s, 1, -1)
.(s+s).find(s, 1, -1)
akan (sangat sedikit) lebih cepat daripada(s+s)[1:-1].find(s)
, setidaknya untuk string yang lebih besar, karena mengiris berarti Anda harus membuat salinan lain (hampir) seluruh string."abcd"
lepas karakter di sebelah kanan, dan tempelkan kembali ke kiri untuk mendapatkan"dabc"
. Prosedur ini disebut memutar string ke kanan oleh 1 karakter . Ulangin
waktu untuk memutar string ke kanan olehn
karakter. Sekarang amati bahwa jika kita memiliki stringk
karakter, berputar ke kanan dengan kelipatan ganda darik
string tidak berubah. Sebuah trivial rotasi string adalah salah satu yang jumlahnya karakter bukan kelipatan dari panjang string.Berikut ini solusi menggunakan ekspresi reguler.
Mengulangi contoh-contoh dalam pertanyaan:
... menghasilkan keluaran ini:
Ekspresi reguler
(.+?)\1+$
dibagi menjadi tiga bagian:(.+?)
adalah grup yang cocok yang mengandung setidaknya satu (tetapi sesedikit mungkin) karakter apa pun (karena+?
tidak serakah ).\1+
memeriksa setidaknya satu pengulangan dari kelompok yang cocok di bagian pertama.$
memeriksa akhir string, untuk memastikan bahwa tidak ada konten tambahan, yang tidak berulang setelah substring berulang (dan menggunakanre.match()
memastikan bahwa tidak ada teks yang tidak berulang sebelum substring berulang).Dalam Python 3.4 dan yang lebih baru, Anda bisa menjatuhkan
$
dan menggunakannyare.fullmatch()
sebagai gantinya, atau (dalam Python apa pun setidaknya sejauh 2.3) pergi ke arah lain dan gunakanre.search()
dengan regex^(.+?)\1+$
, yang semuanya lebih ke selera pribadi daripada yang lain.sumber
Anda dapat membuat pengamatan bahwa agar sebuah string dianggap berulang, panjangnya harus dapat dibagi dengan panjang urutan berulangnya. Mengingat bahwa, di sini adalah solusi yang menghasilkan pembagi panjang dari
1
ken / 2
inklusif, membagi string asli ke substring dengan panjang pembagi, dan menguji kesetaraan hasil set:EDIT: Dalam Python 3,
/
operator telah berubah untuk melakukan pembagian float secara default. Untuk mendapatkanint
pembagian dari Python 2, Anda bisa menggunakan//
operator sebagai gantinya. Terima kasih kepada @ TigerhawkT3 untuk membawa ini menjadi perhatian saya.The
//
Melakukan Operator bilangan bulat divisi di kedua Python 2 dan Python 3, jadi saya telah memperbarui jawaban untuk mendukung kedua versi. Bagian tempat kami menguji untuk melihat apakah semua substring sama sekarang adalah operasi hubung singkatall
dan ekspresi generator.UPDATE: Sebagai tanggapan terhadap perubahan dalam pertanyaan awal, kode sekarang telah diperbarui untuk mengembalikan substring berulang terkecil jika ada dan
None
jika tidak. @ godlygeek telah menyarankan penggunaandivmod
untuk mengurangi jumlah iterasi padadivisors
generator, dan kode telah diperbarui agar sesuai dengan itu juga. Sekarang mengembalikan semua pembagi positifn
dalam urutan menaik, eksklusif untukn
dirinya sendiri.Pembaruan lebih lanjut untuk kinerja tinggi: Setelah beberapa pengujian, saya sampai pada kesimpulan bahwa hanya menguji kesetaraan string memiliki kinerja terbaik dari semua solusi pengiris atau iterator dengan Python. Dengan demikian, saya telah mengambil daun dari buku @ TigerhawkT3 dan memperbarui solusi saya. Sekarang lebih dari 6x lebih cepat dari sebelumnya, terutama lebih cepat dari solusi Tigerhawk tetapi lebih lambat dari David.
sumber
(n/2)
termasuk.n / 2
didivisors()
menjadin // 2
?Berikut adalah beberapa tolok ukur untuk berbagai jawaban untuk pertanyaan ini. Ada beberapa hasil yang mengejutkan, termasuk kinerja yang sangat berbeda tergantung pada string yang diuji.
Beberapa fungsi dimodifikasi untuk bekerja dengan Python 3 (terutama dengan mengganti
/
dengan//
untuk memastikan pembagian integer). Jika Anda melihat sesuatu yang salah, ingin menambahkan fungsi Anda, atau ingin menambahkan string uji lain, ping @ZeroPiraeus di chatroom Python .Singkatnya: ada sekitar 50x perbedaan antara solusi terbaik dan berkinerja terburuk untuk sekumpulan besar contoh data yang disediakan oleh OP di sini (melalui komentar ini ). Solusi David Zhang adalah pemenang yang jelas, mengungguli semua yang lain sekitar 5x untuk set contoh besar.
Beberapa jawaban sangat lambat dalam kasus "tidak cocok" yang sangat besar. Kalau tidak, fungsinya tampaknya sama-sama cocok atau jelas pemenang tergantung pada tes.
Berikut adalah hasilnya, termasuk plot yang dibuat menggunakan matplotlib dan seaborn untuk menunjukkan distribusi yang berbeda:
Corpus 1 (contoh yang disediakan - set kecil)
Corpus 2 (contoh disediakan - set besar)
Corpus 3 (kasus tepi)
Tes dan hasil mentah tersedia di sini .
sumber
Solusi non-regex:
Solusi non-regex yang lebih cepat, terima kasih kepada @ThatWeirdo (lihat komentar):
Solusi di atas sangat jarang lebih lambat daripada yang asli dengan beberapa persen, tetapi biasanya sedikit lebih baik - kadang-kadang jauh lebih cepat. Ini masih tidak lebih cepat dari davidism untuk string yang lebih panjang, dan solusi regex nol lebih unggul untuk string pendek. Itu keluar ke tercepat (menurut tes davidisme di github - lihat jawabannya) dengan string sekitar 1000-1500 karakter. Apapun, itu tercepat tercepat (atau lebih baik) dalam semua kasus yang saya uji. Terima kasih, ThatWeirdo.
Uji:
Hasil:
sumber
repeat('aa')
kembaliNone
len(string[0:i])
selalu sama dengani
(setidaknya dalam hal ini). Mengganti ini, dan juga menyimpanlen(string)
danstring[0:i]
dalam variabel mungkin mempercepat. Juga IMO ini adalah solusi hebat, luar biasa;)Pertama, membagi dua string selama itu duplikat "2 bagian". Ini mengurangi ruang pencarian jika ada jumlah yang berulang. Kemudian, bekerja ke depan untuk menemukan string berulang terkecil, periksa apakah memisahkan string penuh dengan semakin besar sub-string menghasilkan hanya nilai-nilai kosong. Hanya sub-string yang
length // 2
perlu diuji karena apa pun yang tidak akan berulang.Ini mengembalikan kecocokan terpendek atau Tidak ada jika tidak ada kecocokan.
sumber
Masalahnya juga dapat diselesaikan dalam
O(n)
kasus terburuk dengan fungsi awalan.Catatan, mungkin lebih lambat dalam kasus umum (UPD: dan jauh lebih lambat) dibandingkan solusi lain yang tergantung pada jumlah pembagi dari
n
, tetapi biasanya menemukan gagal cepat, saya pikir salah satu kasus buruk bagi mereka akanaaa....aab
, di mana adan - 1 = 2 * 3 * 5 * 7 ... *p_n - 1
a
'sPertama-tama Anda perlu menghitung fungsi awalan
maka baik tidak ada jawaban atau periode terpendek adalah
dan Anda hanya perlu memeriksa apakah
k != n and n % k == 0
(jikak != n and n % k == 0
jawabannya adalahs[:k]
, kalau tidak, tidak ada jawabanAnda dapat memeriksa buktinya di sini (dalam bahasa Rusia, tetapi penerjemah online mungkin akan melakukan triknya)
sumber
prefix_function()
Python Anda tidak valid: Anda memiliki titik dua pada kolomwhile
danif
pernyataan Anda, dan&&
bukannyaand
. Setelah memperbaikinya, gagal denganUnboundLocalError: local variable 'i' referenced before assignment
karena garisfor i in range(i, n):
.prefix_function()
untuk mengembalikan hasil yang serupa ke jawaban lain - baik substring terpendek atauNone
- Saya akan memasukkannya dalam tolok ukur yang direvisi yang saya kumpulkan.Versi ini hanya mencoba panjang urutan kandidat yang merupakan faktor dari panjang string; dan menggunakan
*
operator untuk membangun string full-length dari urutan kandidat:Terima kasih kepada TigerhawkT3 karena memperhatikan bahwa
length // 2
tanpa+ 1
akan gagal untuk mencocokkanabab
kasus ini.sumber
range
bataslength//2
, seperti yang saya lakukan - Anda harus mengubahnya menjadilength//2+1
jika Anda ingin menangkap string yang persis dua kali lipat (misalnya'aabaab'
).Inilah solusi lurus ke depan, tanpa regex.
Untuk substring
s
mulai dari indeks nol, dengan panjang 1 hinggalen(s)
, periksa apakah substring itu,substr
adalah pola yang berulang. Pemeriksaan ini dapat dilakukan dengan menggabungkansubstr
denganratio
waktu itu sendiri , sehingga panjang string yang dibentuk sama dengan panjangs
. Karenanyaratio=len(s)/len(substr)
.Kembali saat substring pertama ditemukan. Ini akan memberikan substring sekecil mungkin, jika ada.
sumber
Saya mulai dengan lebih dari delapan solusi untuk masalah ini. Beberapa didasarkan pada regex (match, findall, split), beberapa slicing string dan pengujian, dan beberapa dengan metode string (find, count, split). Masing-masing memiliki manfaat dalam kejelasan kode, ukuran kode, kecepatan dan konsumsi memori. Saya akan memposting jawaban saya di sini ketika saya perhatikan bahwa kecepatan eksekusi diperingkat penting, jadi saya melakukan lebih banyak pengujian dan peningkatan untuk sampai pada ini:
Jawaban ini tampaknya mirip dengan beberapa jawaban lain di sini, tetapi memiliki beberapa optimisasi kecepatan yang belum digunakan orang lain:
xrange
sedikit lebih cepat dalam aplikasi ini,s[:n]
secara langsung, kami menghindari membuat variabel di setiap loop.Saya akan tertarik untuk melihat bagaimana kinerjanya dalam tes standar dengan perangkat keras umum. Saya percaya itu akan kekurangan algoritma David Zhang yang sangat baik dalam sebagian besar tes, tetapi seharusnya cukup cepat jika tidak.
Saya menemukan masalah ini sangat kontra-intuitif. Solusi yang saya pikir akan cepat lambat. Solusi yang tampak lambat itu cepat! Tampaknya penciptaan string Python dengan operator multiply dan perbandingan string sangat dioptimalkan.
sumber
statistics
modul baru ), jadi saya harus mengubah/
s ke//
s dan gantixrange()
denganrange()
(yang berperilaku seperti 2.xxrange()
di 3.x).//
di 3.x adalah pembagian integer (seperti perilaku 2.x/
), sedangkan 3.x/
adalah divisi float (yang saya yakin akan jauh lebih lambat bahkan jika itu tidak merusak solusi Anda dengan menyebabkan upaya untuk menggunakan float sebagai indeks). Seperti disebutkan, 3.xrange()
sama dengan 2.xxrange()
; tidak ada yang setara dengan 2.xrange()
di 3.x. Jadi saya tidak berpikir itulah penyebab perbedaan antara tolok ukur dan timing yang Anda buat. Mungkin hanya keseluruhan 3.x lebih lambat dari 2.x (atau mungkin mesin Anda lebih cepat dari milikku).Fungsi ini berjalan sangat cepat (diuji dan ini lebih dari 3 kali lebih cepat daripada solusi tercepat di sini pada string dengan lebih dari 100 ribu karakter dan perbedaannya semakin besar semakin lama pola pengulangannya). Mencoba meminimalkan jumlah perbandingan yang diperlukan untuk mendapatkan jawaban:
Perhatikan bahwa misalnya untuk string dengan panjang 8 ia hanya memeriksa fragmen ukuran 4 dan tidak perlu menguji lebih lanjut karena pola panjang 1 atau 2 akan menghasilkan pola berulang panjang 4:
sumber
Dalam jawaban David Zhang jika kita memiliki semacam penyangga bundar, ini tidak akan berfungsi:
principal_period('6210045662100456621004566210045662100456621')
karena permulaannya621
, di mana saya ingin meludahkannya:00456621
.Memperluas solusinya, kita dapat menggunakan yang berikut:
sumber
Berikut ini adalah kode dalam python yang memeriksa pengulangan sub string dalam string utama yang diberikan oleh pengguna .
Masukan :
Keluaran :
Masukan :
Keluaran :
sumber