Misalnya, saya punya daftar:
a[0] = [1, 1, 1, 0, 0]
a[1] = [1, 1, 0, 0, 1]
a[2] = [0, 1, 1, 1, 0]
# and so on
Mereka tampaknya berbeda, tetapi jika seharusnya bahwa awal dan akhir terhubung, maka mereka identik secara sirkuler .
Masalahnya adalah, setiap daftar yang saya miliki memiliki panjang 55 dan hanya berisi tiga dan 52 nol di dalamnya. Tanpa kondisi lingkaran, ada 26.235 (55 pilih 3) daftar. Namun, jika kondisi 'melingkar' ada, ada sejumlah besar daftar identik sirkuler
Saat ini saya memeriksa identitas sirkuler dengan mengikuti:
def is_dup(a, b):
for i in range(len(a)):
if a == list(numpy.roll(b, i)): # shift b circularly by i
return True
return False
Fungsi ini membutuhkan 55 operasi pergantian siklik pada kondisi terburuk. Dan ada 26.235 daftar untuk dibandingkan satu sama lain. Singkatnya, saya perlu 55 * 26.235 * (26.235 - 1) / 2 = 18.926.847.225 perhitungan. Sekitar 20 Giga!
Apakah ada cara yang baik untuk melakukannya dengan perhitungan yang lebih sedikit? Atau tipe data apa saja yang mendukung sirkular ?
Jawaban:
Pertama, ini dapat dilakukan dalam
O(n)
hal panjang daftar. Anda dapat melihat bahwa jika Anda akan menduplikasi daftar Anda 2 kali ([1, 2, 3]
) akan[1, 2, 3, 1, 2, 3]
maka daftar baru Anda pasti akan menyimpan semua daftar siklik yang mungkin.Jadi yang Anda butuhkan adalah memeriksa apakah daftar yang Anda cari ada dalam 2 kali dari daftar awal Anda. Dalam python Anda dapat mencapai ini dengan cara berikut (dengan asumsi bahwa panjangnya sama).
Beberapa penjelasan tentang oneliner saya:
list * 2
akan menggabungkan daftar dengan dirinya sendiri,map(str, [1, 2])
mengubah semua angka menjadi string dan' '.join()
akan mengubah array['1', '2', '111']
menjadi string'1 2 111'
.Seperti yang ditunjukkan oleh beberapa orang di komentar, oneliner berpotensi memberikan beberapa hal positif yang salah, sehingga untuk mencakup semua kemungkinan kasus tepi:
PS1 ketika berbicara tentang kompleksitas waktu, perlu diperhatikan bahwa
O(n)
akan tercapai jika substring dapat ditemukan dalamO(n)
waktu. Tidak selalu demikian dan tergantung pada implementasi dalam bahasa Anda ( meskipun berpotensi dapat dilakukan secara linear waktu KMP misalnya).PS2 untuk orang-orang yang takut operasi string dan karena fakta ini berpikir bahwa jawabannya tidak baik. Yang penting adalah kompleksitas dan kecepatan. Algoritma ini berpotensi berjalan dalam ruang
O(n)
dan waktuO(n)
yang membuatnya jauh lebih baik daripada apa pun diO(n^2)
domain. Untuk melihatnya sendiri, Anda dapat menjalankan tolok ukur kecil (membuat daftar acak muncul elemen pertama dan menambahkannya sampai akhir sehingga membuat daftar siklik. Anda bebas melakukan manipulasi Anda sendiri)0,3 detik di mesin saya. Tidak terlalu lama. Sekarang coba bandingkan ini dengan
O(n^2)
solusi. Saat membandingkannya, Anda dapat melakukan perjalanan dari AS ke Australia (kemungkinan besar dengan kapal pesiar)sumber
Tidak cukup berpengetahuan luas dalam Python untuk menjawab ini dalam bahasa yang Anda minta, tetapi dalam C / C ++, mengingat parameter pertanyaan Anda, saya akan mengonversi nol dan yang menjadi bit dan mendorong mereka ke bit paling tidak signifikan dari sebuah uint64_t. Ini akan memungkinkan Anda untuk membandingkan semua 55 bit dalam sekali gerakan - 1 jam.
Sangat cepat, dan semuanya akan sesuai dengan cache on-chip (209.880 byte). Dukungan perangkat keras untuk menggeser semua 55 daftar anggota secara bersamaan hanya tersedia di register CPU. Hal yang sama berlaku untuk membandingkan semua 55 anggota secara bersamaan. Ini memungkinkan pemetaan 1-untuk-1 masalah ke solusi perangkat lunak. (dan menggunakan register 256 bit SIMD / SSE, hingga 256 anggota jika diperlukan). Akibatnya, kode ini segera jelas bagi pembaca.
Anda mungkin dapat mengimplementasikan ini dengan Python, saya hanya tidak tahu cukup baik untuk mengetahui apakah itu mungkin atau bagaimana kinerjanya.
Setelah tidur di atasnya beberapa hal menjadi jelas, dan semuanya menjadi lebih baik.
1.) Sangat mudah untuk memutar daftar yang terhubung secara melingkar menggunakan bit sehingga trik Dali yang sangat pintar tidak diperlukan. Di dalam register 64-bit, penggeseran bit standar akan menyelesaikan rotasi dengan sangat sederhana, dan dalam upaya menjadikan ini lebih ramah Python, dengan menggunakan aritmatika alih-alih bit ops.
2.) Penggeseran bit dapat dilakukan dengan mudah menggunakan membagi dengan 2.
3.) Memeriksa akhir daftar untuk 0 atau 1 dapat dengan mudah dilakukan oleh modulo 2.
4.) "Memindahkan" a 0 ke kepala daftar dari ekor dapat dilakukan dengan membagi dengan 2. Ini karena jika nol benar-benar dipindahkan itu akan membuat bit ke-55 salah, yang sudah dengan tidak melakukan apa-apa sama sekali.
5.) "Memindahkan" 1 ke kepala daftar dari ekor dapat dilakukan dengan membaginya dengan 2 dan menambahkan 18.014.398.509.481.984 - yang merupakan nilai yang dibuat dengan menandai bit ke-55 true dan sisanya salah.
6.) Jika perbandingan jangkar dan terdiri uint64_t BENAR setelah setiap rotasi yang diberikan, istirahat dan kembali BENAR.
Saya akan mengonversi seluruh array daftar ke dalam array uint64_ts tepat di depan untuk menghindari harus melakukan konversi berulang kali.
Setelah menghabiskan beberapa jam mencoba mengoptimalkan kode, mempelajari bahasa rakitan saya bisa mencukur 20% dari runtime. Saya harus menambahkan bahwa kompiler O / S dan MSVC mendapat pembaruan tengah hari kemarin juga. Untuk alasan apa pun, kualitas kode yang dihasilkan oleh kompiler C meningkat secara dramatis setelah pembaruan (15/11/2014). Run-time sekarang ~ 70 jam, 17 nanodetik untuk menyusun dan membandingkan cincin jangkar dengan semua 55 putaran cincin tes dan NxN dari semua cincin terhadap yang lainnya dilakukan dalam 12,5 detik .
Kode ini sangat ketat, kecuali 4 register yang tidak melakukan 99% dari waktu. Bahasa assembly cocok dengan kode C hampir baris untuk baris. Sangat mudah dibaca dan dimengerti. Proyek perakitan yang bagus jika seseorang mengajari mereka sendiri.
Perangkat kerasnya adalah Hazwell i7, MSVC 64-bit, optimisasi penuh.
sumber
Membaca yang tersirat, sepertinya Anda mencoba untuk menghitung satu perwakilan dari setiap kelas string ekivalen lingkaran dengan 3 yang dan 52 nol. Mari beralih dari representasi padat untuk satu jarang (set tiga angka di
range(55)
). Dalam representasi ini, pergeseran lingkarans
olehk
diberikan oleh pemahamanset((i + k) % 55 for i in s)
. Perwakilan minimum leksikografi di kelas selalu berisi posisi 0. Mengingat satu set bentuk{0, i, j}
dengan0 < i < j
, kandidat lainnya untuk minimum di kelas yang{0, j - i, 55 - i}
dan{0, 55 - j, 55 + i - j}
. Oleh karena itu, kita perlu(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))
minimum untuk yang asli. Ini beberapa kode enumerasi.sumber
Ulangi array pertama, lalu gunakan algoritma Z (O (n) waktu) untuk menemukan array kedua di dalam array pertama.
(Catatan: Anda tidak perlu menyalin secara fisik array pertama. Anda hanya dapat membungkus selama pencocokan.)
Yang menyenangkan tentang algoritma Z adalah sangat sederhana dibandingkan dengan KMP, BM, dll.
Namun, jika Anda merasa ambisius, Anda dapat melakukan pencocokan string dalam waktu linier dan ruang konstan -
strstr
, misalnya, melakukan ini. Menerapkannya akan lebih menyakitkan.sumber
Menindaklanjuti solusi yang sangat cerdas dari Salvador Dali, cara terbaik untuk menanganinya adalah memastikan semua elemen memiliki panjang yang sama, serta kedua LISTS memiliki panjang yang sama.
Tidak ada petunjuk apakah ini lebih cepat atau lebih lambat dari solusi regex yang direkomendasikan AshwiniChaudhary dalam jawaban Salvador Dali, yang berbunyi:
sumber
str.format
n
waktu untuk memformat string yang dihasilkan. AKU SUDAH .... :)Mengingat bahwa Anda perlu melakukan begitu banyak perbandingan, mungkinkah ini layak untuk Anda saat mengambil langkah awal melalui daftar Anda untuk mengubahnya menjadi semacam bentuk kanonik yang dapat dengan mudah dibandingkan?
Apakah Anda mencoba mendapatkan daftar unik yang melingkar? Jika demikian, Anda dapat membuangnya ke dalam set setelah mengonversi ke tupel.
Permintaan maaf kepada David Eisenstat karena tidak menemukan jawaban yang sama.
sumber
Anda dapat menggulung satu daftar seperti ini:
sumber
Konversi terlebih dahulu setiap elemen daftar Anda (dalam salinan jika perlu) untuk itu versi diputar yang leksikal terbesar.
Kemudian urutkan daftar daftar yang dihasilkan (mempertahankan indeks ke posisi daftar asli) dan menyatukan daftar diurutkan, menandai semua duplikat dalam daftar asli sesuai kebutuhan.
sumber
Membonceng pengamatan @ SalvadorDali tentang mencari kecocokan dalam setiap irisan berukuran panjang dalam b + b, berikut adalah solusi menggunakan operasi daftar saja.
Pendekatan 2: [dihapus]
sumber
rollmatch([1, 0, 1, 1], [0, 1, 1, 1])
.Bukan jawaban yang lengkap dan berdiri bebas, tetapi pada topik optimisasi dengan mengurangi perbandingan, saya juga memikirkan representasi yang dinormalisasi.
Yaitu, jika alfabet input Anda adalah {0, 1}, Anda dapat mengurangi jumlah permutasi yang diizinkan secara signifikan. Putar daftar pertama ke bentuk (pseudo-) yang dinormalisasi (mengingat distribusi dalam pertanyaan Anda, saya akan memilih satu di mana salah satu dari 1 bit berada di paling kiri, dan salah satu dari 0 bit ada di paling kanan). Sekarang sebelum setiap perbandingan, berturut-turut putar daftar lainnya melalui posisi yang mungkin dengan pola penyelarasan yang sama.
Sebagai contoh, jika Anda memiliki total empat 1 bit, bisa ada paling banyak 4 permutasi dengan penyelarasan ini, dan jika Anda memiliki kelompok 1 bit yang berdekatan, setiap bit tambahan dalam sebuah cluster mengurangi jumlah posisi.
Ini digeneralisasikan ke huruf yang lebih besar dan pola penyelarasan yang berbeda; tantangan utamanya adalah menemukan normalisasi yang baik dengan hanya beberapa kemungkinan representasi. Idealnya, itu akan menjadi normalisasi yang tepat, dengan satu representasi unik, tetapi mengingat masalahnya, saya pikir itu tidak mungkin.
sumber
Membangun lebih jauh jawaban RocketRoy: Konversi semua daftar Anda di muka menjadi angka 64 bit yang tidak ditandatangani. Untuk setiap daftar, putar 55 bit itu di sekitar untuk menemukan nilai numerik terkecil.
Anda sekarang dibiarkan dengan nilai 64 bit tak bertanda tunggal untuk setiap daftar yang dapat Anda bandingkan langsung dengan nilai daftar lainnya. Fungsi is_circular_identical () tidak diperlukan lagi.
(Pada intinya, Anda membuat nilai identitas untuk daftar Anda yang tidak terpengaruh oleh rotasi elemen daftar) Itu bahkan akan berfungsi jika Anda memiliki nomor sewenang-wenang di dalam daftar Anda.
sumber
Ini adalah ide yang sama dari Salvador Dali tetapi tidak perlu konversi string. Di belakang adalah ide pemulihan KMP yang sama untuk menghindari inspeksi shift yang tidak mungkin. Mereka hanya memanggil KMPModified (list1, list2 + list2).
Semoga bantuan ini!
sumber
Menyederhanakan Masalah
(0,1)
1
menjadi hitungan0
menjadi hitungan negatifContoh
Memeriksa proses
Pegangan
lookup
danlook-ahead
Pseudo-Code
Fungsi
MAP_LIST(LIST A):LIST
PETA UNSUR KONSQUETIF SEBAGAI NEGARA DALAM DAFTAR BARULOOKUP_INDEX(LIST A, INTEGER E):LIST
KEMBALI DAFTAR INDIKASI DI MANA UNSUR-UNSURE
DI DALAM DAFTARA
COUNT_CHAR(LIST A , INTEGER E):INTEGER
COUNT BAGAIMANA BANYAK KALI SEBUAH UNSURE
TERJADI DALAM DAFTARA
ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN
PERIKSA JIKAB[I]
SETIAP DENGANA[0]
N-GRAM
DALAM KEDUA ARAHAkhirnya
Jika ukuran daftar akan sangat besar atau jika elemen yang kita mulai periksa siklusnya sering tinggi, maka kita dapat melakukan hal berikut:
Cari item yang paling jarang di daftar pertama untuk memulai
meningkatkan parameter n-gram N untuk menurunkan kemungkinan melalui pemeriksaan linear
sumber
"Bentuk kanonik" yang efisien, cepat untuk dihitung untuk daftar yang dimaksud dapat diturunkan sebagai:
a
) harus antara18
dan52
(inklusif). Encode ulang sebagai antara0
dan34
.b
) harus antara0
dan26
, tetapi tidak masalah.52 - (a + b)
dan tidak menambah informasiBentuk kanonik adalah bilangan bulat
b * 35 + a
, yang berada di antara0
dan936
(inklusif), yang cukup kompak (ada daftar477
melingkar-unik total).sumber
Saya menulis solusi langsung yang membandingkan daftar dan hanya meningkatkan (dan membungkus) indeks dari nilai yang dibandingkan untuk setiap iterasi.
Saya tidak tahu python dengan baik, jadi saya menulisnya di Jawa, tapi itu sangat sederhana sehingga harus mudah untuk beradaptasi dengan bahasa lain
Dengan ini, Anda juga dapat membandingkan daftar jenis lainnya.
sumber
Seperti yang disebutkan orang lain, setelah Anda menemukan rotasi daftar yang dinormalisasi, Anda dapat membandingkannya.
Berikut ini beberapa kode kerja yang melakukan ini, Metode dasar adalah menemukan rotasi yang dinormalisasi untuk setiap daftar dan membandingkan:
Perhatikan bahwa metode ini tidak bergantung pada angka, Anda dapat mengirimkan daftar string (nilai apa pun yang dapat dibandingkan).
Alih-alih melakukan pencarian daftar-dalam-daftar, kami tahu kami ingin daftar dimulai dengan nilai minimum - sehingga kami dapat mengulangi nilai-nilai minimum, mencari sampai kami menemukan mana yang memiliki nilai berturut-turut terendah, menyimpannya untuk perbandingan lebih lanjut sampai kita mendapatkan yang terbaik.
Ada banyak peluang untuk keluar lebih awal saat menghitung indeks, detail beberapa optimasi.
Perhatikan bahwa dalam Python pencarian daftar-dalam-daftar mungkin lebih cepat, namun saya tertarik untuk menemukan algoritma yang efisien - yang dapat digunakan dalam bahasa lain juga. Juga, ada beberapa keuntungan untuk menghindari membuat daftar baru.
Lihat: cuplikan ini untuk beberapa tes / contoh lainnya.
sumber
Anda dapat memeriksa untuk melihat apakah daftar A sama dengan perubahan siklik daftar B dalam waktu O (N) yang diharapkan dengan cukup mudah.
Saya akan menggunakan fungsi hash polinomial untuk menghitung hash dari daftar A, dan setiap perubahan siklik daftar B. Di mana pergeseran daftar B memiliki hash yang sama dengan daftar A, saya akan membandingkan elemen aktual untuk melihat apakah mereka sama. .
Alasan ini cepat adalah bahwa dengan fungsi hash polinomial (yang sangat umum!), Anda dapat menghitung hash dari setiap perubahan siklik dari sebelumnya dalam waktu yang konstan, sehingga Anda dapat menghitung hash untuk semua pergeseran siklik di O ( N) waktu.
Ini berfungsi seperti ini:
Katakanlah B memiliki elemen N, maka hash B menggunakan prime P adalah:
Ini adalah cara yang dioptimalkan untuk mengevaluasi polinomial dalam P, dan setara dengan:
Perhatikan bagaimana setiap B [i] dikalikan dengan P ^ (N-1-i). Jika kita menggeser B ke kiri dengan 1, maka setiap setiap B [i] akan dikalikan dengan P tambahan, kecuali yang pertama. Karena multiplikasi mendistribusikan lebih dari tambahan, kita dapat melipatgandakan semua komponen sekaligus hanya dengan mengalikan seluruh hash, dan kemudian memperbaiki faktor untuk elemen pertama.
Hash dari shift kiri B hanya
Pergeseran kiri kedua:
dan seterusnya...
CATATAN: semua matematika di atas dilakukan modulo beberapa ukuran kata mesin, dan Anda hanya perlu menghitung P ^ N satu kali.
sumber
Untuk merekatkan cara paling pythonic untuk melakukannya, gunakan set!
sumber