Saya menggunakan Python 3.5.2
Saya punya dua daftar
- daftar sekitar 750.000 "kalimat" (string panjang)
- daftar sekitar 20.000 "kata" yang ingin saya hapus dari 750.000 kalimat saya
Jadi, saya harus mengulangi 750.000 kalimat dan melakukan sekitar 20.000 penggantian, tetapi HANYA jika kata-kata saya sebenarnya "kata" dan bukan bagian dari rangkaian karakter yang lebih besar.
Saya melakukan ini dengan pra-kompilasi kata-kata saya sehingga mereka diapit oleh \b
metacharacter
compiled_words = [re.compile(r'\b' + word + r'\b') for word in my20000words]
Kemudian saya mengulangi "kalimat" saya
import re
for sentence in sentences:
for word in compiled_words:
sentence = re.sub(word, "", sentence)
# put sentence into a growing list
Loop bersarang ini memproses sekitar 50 kalimat per detik , yang bagus, tetapi masih butuh beberapa jam untuk memproses semua kalimat saya.
Apakah ada cara untuk menggunakan
str.replace
metode ini (yang saya percaya lebih cepat), tetapi masih membutuhkan penggantian yang hanya terjadi pada batas kata ?Atau, apakah ada cara untuk mempercepat
re.sub
metode ini? Saya telah meningkatkan kecepatan sedikit dengan melompatire.sub
jika panjang kata saya> dari panjang kalimat saya, tapi itu tidak banyak perbaikan.
Terima kasih atas sarannya.
multiprocessing
(yaitu beberapa proses Python).Jawaban:
Satu hal yang dapat Anda coba adalah mengkompilasi satu pola tunggal seperti
"\b(word1|word2|word3)\b"
.Karena
re
bergantung pada kode C untuk melakukan pencocokan yang sebenarnya, penghematan bisa menjadi dramatis.Sebagaimana @pvg tunjukkan dalam komentar, itu juga mendapat manfaat dari pencocokan satu pass.
Jika kata-kata Anda bukan regex, jawaban Eric lebih cepat.
sumber
s/They actually use/They actually could in theory sometimes use/
. Apakah Anda punya alasan untuk meyakini implementasi Python melakukan hal lain selain loop di sini?TLDR
Gunakan metode ini (dengan set lookup) jika Anda menginginkan solusi tercepat. Untuk dataset yang mirip dengan OP, kira-kira 2000 kali lebih cepat dari jawaban yang diterima.
Jika Anda bersikeras menggunakan regex untuk pencarian, gunakan versi berbasis trie ini , yang masih 1000 kali lebih cepat daripada regex union.
Teori
Jika kalimat Anda bukan string yang besar, mungkin layak untuk memproses lebih dari 50 per detik.
Jika Anda menyimpan semua kata yang dilarang ke set, akan sangat cepat untuk memeriksa apakah kata lain termasuk dalam set itu.
Kemas logika ke dalam fungsi, berikan fungsi ini sebagai argumen
re.sub
dan Anda selesai!Kode
Kalimat yang dikonversi adalah:
Perhatikan bahwa:
lower()
)""
mungkin menyisakan dua spasi (seperti dalam kode Anda)\w+
juga cocok dengan karakter beraksen (mis"ångström"
.).Performa
Ada sejuta kalimat,
banned_words
memiliki hampir 100.000 kata dan skrip berjalan dalam waktu kurang dari 7s.Sebagai perbandingan, jawaban Liteye membutuhkan 160 untuk 10 ribu kalimat.
Dengan
n
menjadi jumlah total kata danm
jumlah kata yang dilarang, kode OP dan Liteye adalahO(n*m)
.Sebagai perbandingan, kode saya harus dijalankan
O(n+m)
. Menimbang bahwa ada lebih banyak kalimat daripada kata-kata yang dilarang, algoritme menjadiO(n)
.Tes serikat Regex
Apa kompleksitas pencarian regex dengan suatu
'\b(word1|word2|...|wordN)\b'
pola? Apakah ituO(N)
atauO(1)
?Cukup sulit untuk memahami cara kerja mesin regex, jadi mari kita tulis tes sederhana.
Kode ini mengekstrak
10**i
kata-kata bahasa Inggris acak ke dalam daftar. Itu menciptakan serikat regex yang sesuai, dan mengujinya dengan kata-kata yang berbeda:#
)Ini menghasilkan:
Jadi sepertinya pencarian untuk satu kata dengan
'\b(word1|word2|...|wordN)\b'
pola memiliki:O(1)
kasus terbaikO(n/2)
kasus rata-rata, yang masihO(n)
O(n)
kasus terburukHasil ini konsisten dengan pencarian loop sederhana.
Alternatif yang jauh lebih cepat daripada gabungan regex adalah membuat pola regex dari trie .
sumber
O(1)
klaim yang menyesatkan itu , jawaban Anda pasti layak dipilih.TLDR
Gunakan metode ini jika Anda menginginkan solusi berbasis regex tercepat. Untuk dataset yang mirip dengan OP, kira-kira 1000 kali lebih cepat dari jawaban yang diterima.
Jika Anda tidak peduli dengan regex, gunakan versi set-based ini , yang 2000 kali lebih cepat daripada regex union.
Dioptimalkan Regex dengan Trie
Sebuah sederhana serikat Regex pendekatan menjadi lambat dengan banyak kata-kata dilarang, karena mesin regex tidak melakukan pekerjaan yang sangat baik mengoptimalkan pola.
Dimungkinkan untuk membuat Trie dengan semua kata yang dilarang dan menulis regex yang sesuai. Trie atau regex yang dihasilkan tidak benar-benar dapat dibaca oleh manusia, tetapi mereka memungkinkan pencarian dan pencocokan yang sangat cepat.
Contoh
Daftar ini dikonversi menjadi trie:
Dan kemudian ke pola regex ini:
Keuntungan besar adalah untuk menguji apakah
zoo
cocok, mesin regex hanya perlu membandingkan karakter pertama (tidak cocok), daripada mencoba 5 kata . Ini adalah kerja keras yang berlebihan untuk 5 kata, tetapi ini menunjukkan hasil yang menjanjikan untuk ribuan kata.Perhatikan bahwa
(?:)
grup yang tidak menangkap digunakan karena:foobar|baz
akan cocokfoobar
ataubaz
, tetapi tidakfoobaz
foo(bar|baz)
akan menyimpan informasi yang tidak dibutuhkan ke grup penangkap .Kode
Inilah inti yang sedikit dimodifikasi , yang bisa kita gunakan sebagai
trie.py
perpustakaan:Uji
Inilah tes kecil (sama dengan yang ini ):
Ini menghasilkan:
Untuk info, regex dimulai seperti ini:
Ini benar-benar tidak dapat dibaca, tetapi untuk daftar 100000 kata yang dilarang, rege Trie ini 1000 kali lebih cepat daripada gabungan regex sederhana!
Berikut diagram dari trie lengkap, diekspor dengan trie-python-graphviz dan graphviz
twopi
:sumber
|
tetapi menangkap kelompok tidak diperlukan untuk tujuan kita sama sekali. Mereka hanya memperlambat proses dan menggunakan lebih banyak memori tanpa manfaat.\b
( batas kata ). Jika daftar itu['apple', 'banana']
, itu akan menggantikan kata-kata yang persisapple
ataubanana
, tetapi tidaknana
,bana
ataupineapple
.Satu hal yang Anda mungkin ingin coba adalah pra-pemrosesan kalimat untuk menyandikan kata batas. Pada dasarnya mengubah setiap kalimat menjadi daftar kata-kata dengan memisahkan batas kata.
Ini harus lebih cepat, karena untuk memproses kalimat, Anda hanya perlu menelusuri setiap kata dan memeriksa apakah itu cocok.
Saat ini pencarian regex harus melalui seluruh string lagi setiap kali, mencari batas kata, dan kemudian "membuang" hasil pekerjaan ini sebelum lulus berikutnya.
sumber
Nah, inilah solusi cepat dan mudah, dengan set tes.
Strategi kemenangan:
re.sub ("\ w +", repl, kalimat) mencari kata-kata.
"repl" bisa menjadi callable. Saya menggunakan fungsi yang melakukan pencarian dict, dan dict berisi kata-kata untuk dicari dan diganti.
Ini adalah solusi paling sederhana dan tercepat (lihat fungsi replace4 dalam kode contoh di bawah).
Kedua terbaik
Idenya adalah untuk membagi kalimat menjadi kata-kata, menggunakan re.split, sambil melestarikan pemisah untuk merekonstruksi kalimat nanti. Kemudian, penggantian dilakukan dengan pencarian dict sederhana.
(lihat fungsi replace3 dalam kode contoh di bawah).
Pengaturan waktu misalnya fungsi:
... dan kode:
Sunting: Anda juga dapat mengabaikan huruf kecil saat memeriksa apakah Anda lulus daftar Kalimat huruf kecil dan mengedit balasan
sumber
replace4
dan kode saya memiliki kinerja yang serupa.repl(m):
dan bagaimana Anda menetapkanm
dalam fungsi replace4error: unbalanced parenthesis
untuk saluranpatterns_comp = [ (re.compile("\\b"+search+"\\b"), repl) for search, repl in patterns ]
Mungkin Python bukan alat yang tepat di sini. Inilah satu dengan toolchain Unix
dengan asumsi file daftar hitam Anda diproses terlebih dahulu dengan batas kata ditambahkan. Langkah-langkahnya adalah: mengonversi file menjadi dua spasi, membagi setiap kalimat menjadi satu kata per baris, menghapus secara massal kata-kata daftar hitam dari file, dan menggabungkan kembali baris.
Ini harus menjalankan setidaknya urutan besarnya lebih cepat.
Untuk memproses ulang file daftar hitam dari kata-kata (satu kata per baris)
sumber
Bagaimana dengan ini:
Solusi ini terpecah pada batas kata dan mencari setiap kata dalam satu set. Mereka harus lebih cepat daripada penggantian kata alternatif (solusi Liteyes 'karena solusi ini adalah di
O(n)
mana n adalah ukuran input karenaamortized O(1)
pencarian yang diatur, sementara menggunakan regex alternatif akan menyebabkan mesin regex harus memeriksa kecocokan kata pada setiap karakter daripada hanya pada batas kata. Solusi saya adalah berhati-hati untuk melestarikan spasi putih yang digunakan dalam teks asli (yaitu tidak mengkompresi spasi putih dan mempertahankan tab, baris baru, dan karakter spasi putih lainnya), tetapi jika Anda memutuskan bahwa Anda tidak peduli, itu harus cukup mudah untuk menghapusnya dari output.Saya menguji pada corpus.txt, yang merupakan gabungan dari beberapa eBook yang diunduh dari Proyek Gutenberg, dan banned_words.txt adalah 20.000 kata yang dipilih secara acak dari daftar kata Ubuntu (/ usr / share / dict / american-english). Diperlukan sekitar 30 detik untuk memproses 862462 kalimat (dan setengahnya di PyPy). Saya telah mendefinisikan kalimat sebagai sesuatu yang dipisahkan oleh "."
PyPy khususnya mendapat manfaat lebih dari pendekatan kedua, sementara CPython bernasib lebih baik pada pendekatan pertama. Kode di atas harus berfungsi pada Python 2 dan 3.
sumber
\W+
dasarnya sepertisub
pada\w+
, kan?Pendekatan praktis
Solusi yang dijelaskan di bawah ini menggunakan banyak memori untuk menyimpan semua teks pada string yang sama dan untuk mengurangi tingkat kompleksitas. Jika RAM bermasalah, pikirkan dua kali sebelum menggunakannya.
Dengan
join
/split
trik Anda dapat menghindari loop sama sekali yang seharusnya mempercepat algoritma.|
pernyataan regex "atau":Performa
"".join
kompleksitasnya adalah O (n). Ini cukup intuitif tetapi ada kutipan singkat dari sumber:Oleh karena itu dengan
join/split
Anda memiliki O (kata) + 2 * O (kalimat) yang masih linear kompleksitas vs 2 * O (N 2 ) dengan pendekatan awal.tapi jangan gunakan multithreading. GIL akan memblokir setiap operasi karena tugas Anda benar-benar terikat CPU sehingga GIL tidak memiliki kesempatan untuk dirilis tetapi setiap utas akan mengirimkan kutu secara bersamaan yang menyebabkan upaya ekstra dan bahkan menyebabkan operasi hingga tak terbatas.
sumber
Gabungkan semua kalimat Anda menjadi satu dokumen. Gunakan implementasi algoritma Aho-Corasick ( berikut ini ) untuk menemukan semua kata "buruk" Anda. Lintasi file, ganti setiap kata buruk, perbarui offset kata-kata yang ditemukan yang mengikuti dll.
sumber