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
import re
with open('/usr/share/dict/american-english') as wordbook:
banned_words = set(word.strip().lower() for word in wordbook)
def delete_banned_words(matchobj):
word = matchobj.group(0)
if word.lower() in banned_words:
return ""
else:
return word
sentences = ["I'm eric. Welcome here!", "Another boring sentence.",
"GiraffeElephantBoat", "sfgsdg sdwerha aswertwe"] * 250000
word_pattern = re.compile('\w+')
for sentence in sentences:
sentence = word_pattern.sub(delete_banned_words, sentence)
Kalimat yang dikonversi adalah:
' . !
.
GiraffeElephantBoat
sfgsdg sdwerha aswertwe
Perhatikan bahwa:
- pencarian bersifat case-insensitive (terima kasih kepada
lower()
)
- mengganti kata dengan
""
mungkin menyisakan dua spasi (seperti dalam kode Anda)
- Dengan python3,
\w+
juga cocok dengan karakter beraksen (mis "ångström"
.).
- Setiap karakter non-kata (tab, spasi, baris baru, tanda, ...) akan tetap tidak tersentuh.
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 dan m
jumlah kata yang dilarang, kode OP dan Liteye adalah O(n*m)
.
Sebagai perbandingan, kode saya harus dijalankan O(n+m)
. Menimbang bahwa ada lebih banyak kalimat daripada kata-kata yang dilarang, algoritme menjadi O(n)
.
Tes serikat Regex
Apa kompleksitas pencarian regex dengan suatu '\b(word1|word2|...|wordN)\b'
pola? Apakah itu O(N)
atau O(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:
- satu jelas bukan kata (itu dimulai dengan
#
)
- satu adalah kata pertama dalam daftar
- satu adalah kata terakhir dalam daftar
- yang terlihat seperti sebuah kata tetapi tidak
import re
import timeit
import random
with open('/usr/share/dict/american-english') as wordbook:
english_words = [word.strip().lower() for word in wordbook]
random.shuffle(english_words)
print("First 10 words :")
print(english_words[:10])
test_words = [
("Surely not a word", "#surely_NöTäWORD_so_regex_engine_can_return_fast"),
("First word", english_words[0]),
("Last word", english_words[-1]),
("Almost a word", "couldbeaword")
]
def find(word):
def fun():
return union.match(word)
return fun
for exp in range(1, 6):
print("\nUnion of %d words" % 10**exp)
union = re.compile(r"\b(%s)\b" % '|'.join(english_words[:10**exp]))
for description, test_word in test_words:
time = timeit.timeit(find(test_word), number=1000) * 1000
print(" %-17s : %.1fms" % (description, time))
Ini menghasilkan:
First 10 words :
["geritol's", "sunstroke's", 'fib', 'fergus', 'charms', 'canning', 'supervisor', 'fallaciously', "heritage's", 'pastime']
Union of 10 words
Surely not a word : 0.7ms
First word : 0.8ms
Last word : 0.7ms
Almost a word : 0.7ms
Union of 100 words
Surely not a word : 0.7ms
First word : 1.1ms
Last word : 1.2ms
Almost a word : 1.2ms
Union of 1000 words
Surely not a word : 0.7ms
First word : 0.8ms
Last word : 9.6ms
Almost a word : 10.1ms
Union of 10000 words
Surely not a word : 1.4ms
First word : 1.8ms
Last word : 96.3ms
Almost a word : 116.6ms
Union of 100000 words
Surely not a word : 0.7ms
First word : 0.8ms
Last word : 1227.1ms
Almost a word : 1404.1ms
Jadi sepertinya pencarian untuk satu kata dengan '\b(word1|word2|...|wordN)\b'
pola memiliki:
O(1)
kasus terbaik
O(n/2)
kasus rata-rata, yang masih O(n)
O(n)
kasus terburuk
Hasil ini konsisten dengan pencarian loop sederhana.
Alternatif yang jauh lebih cepat daripada gabungan regex adalah membuat pola regex dari trie .