Python 3 da regex o'zgarishlarini millionlab tezlashtirish

Men Python 3.5.2 dan foydalanmoqdaman

Menda ikkita ro'yxat bor

  • taxminan 750.000 "jumlalar" (uzun qatorlar) ro'yxati
  • mening 750.000 ta jumlalarimni o'chirib tashlamoqchi bo'lgan taxminan 20 000 ta "so'z" ro'yxatini.

So, I have to loop through 750,000 sentences and perform about 20,000 replacements, but ONLY if my words are actually "words" and are not part of a larger string of characters.

Buni oldindan kompilyatsiya qilish so'zlari bilan qilyapman, shuning uchun ular \ b metacharacter

compiled_words = [re.compile(r'\b' + word + r'\b') for word in my20000words]

Keyin men "jumlalar"

import re

for sentence in sentences:
  for word in compiled_words:
    sentence = re.sub(word, "", sentence)
  # put sentence into a growing list

Bu ichki o'tgan loop yaxshi, lekin bu mening barcha jumlalarni qayta ishlash uchun bir necha soat davom etadigan bo'lsa, saniyda 50 ta jumla ga ishlov beradi.

  • Is there a way to using the str.replace method (which I believe is faster), but still requiring that replacements only happen at word boundaries?

  • Alternatively, is there a way to speed up the re.sub method? I have already improved the speed marginally by skipping over re.sub if the length of my word is > than the length of my sentence, but it's not much of an improvement.

Har qanday taklif uchun rahmat.

97
Siz so'zni bekor qilyapsizmi? sklearn, yuqori performanslı buni qilgan vektörlerdir.
qo'shib qo'ydi muallif smci, manba
@MohammadAli: CPU bilan bog'liq ish uchun bu misol bilan bezovta qilmang. Python bytecode (Global Interpreter Lock) amalga oshirilganda katta blokka ega, shuning uchun protsessor uchun ish zarrachasidan foydalana olmaysiz. multiprocessing (ya'ni bir nechta Python jarayonlari) dan foydalanishingiz kerak.
qo'shib qo'ydi muallif Kevin, manba
@MohammadAli Yo'q, yo'q edi. Men unga qarayman. Tavsiya etilgan darslik (lar) bo'lsa, iltimos, maslahat bering. rahmat
qo'shib qo'ydi muallif user36476, manba
Siz regexni registr bo'lmagan registrga kiritib, pvg :-) tomonidan tavsiya etilgan registrni ishlatishingiz mumkin. Faqat regex barcha "\ S +" funktsiyasini o'zgartiradi, agar u setda yoki bo'sh satrda bo'lmasa bir xil mag'lubiyatni qaytaradi. Mening testim (lekin pythonda emas) soniyasiga 10M belgi bilan ishlaydi.
qo'shib qo'ydi muallif Antonín Lejsek, manba
So'zlar qanday ko'rinishga ega? Ular tinish belgisi bormi? So'zlar oralig'idagi bo'sh joy bormi? So'zni bo'sh satr bilan almashtirish ikki bo'shliqni qoldiradi.
qo'shib qo'ydi muallif Eric Duminil, manba
@pvg: Javobni berganimdan keyin sizning sharhingizni o'qib chiqdim. Sizning taklifingiz yaxshi va tegishli kod qabul qilingan echimdan ancha tezroq ko'rinadi.
qo'shib qo'ydi muallif Eric Duminil, manba
@pvg: Men bu haqda ochiq gapirishni xohlamadim, chunki biror narsani o'tkazib yuborgan bo'lardim. 100000 inglizcha so'z bilan re.compile (r "\ b (% s) \ b"% '|' .join (so'zlar), re.IGNORECASE) yordamida 10000 jumlalar. Taqqoslash uchun mening kodim 7 marta 100 marta ko'proq jumlalar oldi.
qo'shib qo'ydi muallif Eric Duminil, manba
@pvg To'g'ri vaqtga aylantirilsa, men uni javobimga qo'yaman. Men OP va Liteye tasdiqlashni so'radim, chunki haqiqatda bo'lmaganida juda sekinroq deb aytishim bilan to'g'ri javobni hurmat qilmayman. Javobni yangilayman, lekin "\ b (word1 | word2 | word3) \ b" - IMHO so'zlar ro'yxatiga qarshi nazoratdan boshqa narsa emas. Cda, regexp bilan, lekin murakkablik hali regexdagi so'zlar soniga lineer tarzda bog'liq.
qo'shib qo'ydi muallif Eric Duminil, manba
@pvg: (word1 | word2 | word3) ga qo'shilmagan so'z uchun, regex hech qanday so'z yo'qligini aytishdan oldin har bir so'zni tekshirishga to'g'ri keladi, to'g'rimi?
qo'shib qo'ydi muallif Eric Duminil, manba
@ user36476: Mening taklifimni sinab ko'rdingizmi? Ma'lumotlaringizni qayta ishlash uchun qancha vaqt kerakligini bilishdan baxtiyorman.
qo'shib qo'ydi muallif Eric Duminil, manba
Xo'sh, bu jumlalar vaqt qancha? 750k chiziqlar ishlov berish uchun soat oladigan ma'lumotlar to'plamiga o'xshamaydi.
qo'shib qo'ydi muallif pvg, manba
Shuningdek, regex bo'lmagan dasturni ham qo'llashingiz mumkin - kirish so'zini so'z bilan aylantirish va har bir to'plam bilan mos kelish. Bu bitta o'tish va hash qidiruvlari juda tezdir.
qo'shib qo'ydi muallif pvg, manba
@ErikDuminil oh, o'yinni qayta tiklash yoqimli. Bu aniqroq va ravshanroq (mening ko'zlarimga), tezroq bo'lgani uchun men uni sinab ko'rmadim. Sizning namunangizda kontaktsiz regexen bilan qanday vaqtga erishdingiz?
qo'shib qo'ydi muallif pvg, manba
@EricDuminil Menimcha, bu javobni vaqtida belgilash bilan birga, aslida bunga munosibdir. Menimcha, regex variant - so'zlar ro'yxatiga qarshi tekshiruv bo'lishi mumkinligi haqida biroz tasavvur qiladigan narsa.
qo'shib qo'ydi muallif pvg, manba
@EricDuminil Kompilyatsiya qilingan regex qisqa tutashuv bo'ladi, shuning uchun u chiziqli ro'yxatni a'zolik taqqoslash bilan bir xil emas. Lekin bu juda yaxshi. Vaqtning haqiqiyligiga kelsak, siz taklif qilgan yechimlardan faqat O'lchovning maxsus ma'lumotlar to'plami uchun tezkorlik bilan ishlaganingiz uchun da'vo qilishingiz shart emas. Bu savol turli xil ko'rinishlarda juda ko'p bo'ladi va undan ko'p javob beradigan javoblardan foydalanishi mumkin. Maqsad, "posterga foydali" emas, aksincha "boshqalarga foydali".
qo'shib qo'ydi muallif pvg, manba
codereview.SE uchun yaxshi savol bo'lishi mumkin
qo'shib qo'ydi muallif Amani Kilumanga, manba
Bu erda birinchi javob ba'zi yaxshi namunalar kodiga ega: stackoverflow.com/questions/2846653/… sizning gaplar majmuasini faqat sizda ishlaydigan CPU yadrolari soni bo'yicha ajratish,
qo'shib qo'ydi muallif Mohammad Ali, manba

9 javoblar

"\ b (word1 | word2 | word3) \ b" kabi bitta naqshni kompilyatsiya qilishingiz mumkin.

Chunki re haqiqiy kodlashni bajarish uchun C kodiga asoslangan, tejamkorlik dramatik bo'lishi mumkin.

Izohlarda @pvg ko'rsatilgandek, u bitta o'tishni moslashdan ham foydalidir.

Agar so'zlaringiz regex emas bo'lsa, Erikning javob tezroq.

99
qo'shib qo'ydi
@Bakuriu: Buning sababi yo'q. aslida amalga oshirishga ishonishingiz kerakligiga ishonchingiz bormi, deb so'radim. mumkin ga shunday munosabatda bo'lishga ishonish uchun asos bor yoki yo'qligini emas. Shaxsan siz hali ham klassik regexni kutmoqchi bo'lgan tarzda lineer vaqtlarda ishlaydigan yagona umumiy dasturiy tilni regeksni amalga oshirishga kirishishim kerak, shuning uchun Python buni bilsa, ba'zi dalillarni ko'rsatish kerak.
qo'shib qo'ydi muallif Mehrdad, manba
@Bakuriu: s/Ular aslida foydalanadi/Ular aslida nazariyada ba'zan foydalanishi mumkin/. Siz Pythonning amalga oshirilishi bu erda loopdan boshqa hech narsa qilmasligiga ishonish uchun biron-bir sababingiz bormi?
qo'shib qo'ydi muallif Mehrdad, manba
@Mehrdad Ha. Albatta, bu hali ham takrorlanadigan vosita, shuning uchun ko'p vaqt talab etadigan ba'zi holatlar mavjud, ammo oddiy regexes ajratish kabi sodda holatlar O (n + m) vaqtini olishi kerakligiga ishonchim komil. n mos keladigan matnning uzunligi va m naqsh uzunligi. Oddiy sodda loop O (n · m) dir. O'Rdagi regex ishlatilganda, biron-bir takrorlashni talab qilmaslik kerak, shuning uchun regexning lineer vaqtni olishini kutmoqdaman.
qo'shib qo'ydi muallif Bakuriu, manba
@ User36476 Regex bo'lmagan yozuv ko'chadan uchun faqat bir yorliq bor. Ular aslida, ehtimol, bir vaqtning o'zida bir naqsh bilan aniq Otada bilan nima ortiq yo'l tezroq qidirish uchun ba'zi aqlli usullari qo'llaniladi.
qo'shib qo'ydi muallif Bakuriu, manba
@Lite, men buni bir vuruş qilaman. Rahmat.
qo'shib qo'ydi muallif user36476, manba
Sizning taklifingiz 4 soatlik ishni 4 daqiqagacha topshirdi! Men barcha 20,000 + regexes bir ulkan regex ichiga qo'shilishga muvaffaq bo'ldi va mening tizza ko'z ko'z emas edi. Yana bir bor rahmat.
qo'shib qo'ydi muallif user36476, manba
O'yin-kulgi uchun men Trie Regexp bilan javobni yozganman. Bu regexp birlashmasidan qariyb 1000 marta tezdir.
qo'shib qo'ydi muallif Eric Duminil, manba
@Lite: javobimga havola uchun rahmat, bu sizning juda adolatli!
qo'shib qo'ydi muallif Eric Duminil, manba
@pvg: "To'la o'tish" bilan nimani nazarda tutasiz?
qo'shib qo'ydi muallif Eric Duminil, manba
@Bakuriu: Men haqiqatan ham shundaymi yoki yo'qmi, bilish uchun juda qiziq bo'lardim. Agar ittifoqdan Trie qurmasa, qanday qilib sodir bo'lishi mumkinligini ko'rmayapman.
qo'shib qo'ydi muallif Eric Duminil, manba
Buni trie dan foydalangan deb o'ylamayman, chunki murakkab naqshlar bo'lishi mumkin. Ammo, bu, albatta, Python-dagi aylana o'rniga C ning loopini ishlatadi.
qo'shib qo'ydi muallif Liteye, manba
Bu faqatgina C impl (bu katta farqni keltirib chiqaruvchi) emas, balki siz ham bitta pass bilan mos tushasiz. Bu savolning variantlari juda tez-tez kelib chiqadi, bu juda aqlli g'oyani u uchun sharxatli SO javob yo'q (yoki ehtimol, bir joyda yashirish mumkin emas) bir oz g'alati.
qo'shib qo'ydi muallif pvg, manba
Menimcha, bu taklifni yanada oqilona amalga oshirishdir. Men regex dasturining 100% ishonchli emasligini bilaman, lekin u chegarani topgunicha satrga o'tadi deb o'ylayman va keyin o'sha nuqtadagi so'zlarning har birini tekshiring. Shunday qilib, chegaralarni faqat bir marta izlash kerak. Bundan tashqari, menimcha, C kodi so'zlarni trio kabi tezkor qidiruvlar bilan ma'lumotlar tuzilishi sifatida kodlashi mumkinmi?
qo'shib qo'ydi muallif Denziloe, manba
Yaxshi nuqta, rahmat. @ user36476 Iltimos, ushbu kod bilan qanday ishlashini bizga xabar qiling, menga qiziq.
qo'shib qo'ydi muallif Denziloe, manba

TLDR

Agar siz eng tezkor hal qilishni xohlasangiz, ushbu usuldan foydalaning. OShga o'xshash ma'lumotlar bazasi uchun qabul qilingan javobdan taxminan 2000 marta tezroq.

Agar siz qo'ng'iroq qilish uchun regexdan foydalanishni talab qilsangiz, bu triete asoslangan versiya , ya'ni regex birlashmasidan 1000 marta tezdir.

Nazariya

Agar sizning jumlalaringiz chiroyli harflar bo'lmasa, ehtimol saniyda 50 dan ortiq ishlov berish mumkin.

Agar barcha taqiqlangan so'zlarni to'plamga yozib olsangiz, ushbu to'plamga boshqa so'z kiritilganligini tekshirish juda tez bo'ladi.

Mantiqni funktsiyani to'plang, bu funktsiyani re.sub argumentiga ayting va tugallang!

Kod

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)

Tarjima jumlalari quyidagilardir:

' .  !
  .
GiraffeElephantBoat
sfgsdg sdwerha aswertwe

Yozib oling:

  • the search is case-insensitive (thanks to lower())
  • replacing a word with "" might leave two spaces (as in your Kod)
  • With python3, \w+ also matches accented characters (e.g. "ångström").
  • Any non-word character (tab, space, newline, marks, ...) will stay untouched.

Ishlash

There are a million sentences, banned_words has almost 100000 words and the script runs in less than 7s.

Taqqoslash uchun Liteynning javobiga 10 mingta jumlaga 160 barobar kerak bo'ldi.

With n being the total amound of words and m the amount of banned words, OP's and Liteye's Kod are O(n*m).

In comparison, my Kod should run in O(n+m). Considering that there are many more sentences than banned words, the algorithm becomes O(n).

Regex birlashma testi

What's the complexity of a regex search with a '\b(word1|word2|...|wordN)\b' pattern? Is it O(N) or O(1)?

Regex dvigatelining ishini tushunish juda qiyin, shuning uchun oddiy sinovni yozaylik.

This Kod extracts 10**i random english words into a list. It creates the corresponding regex union, and tests it with different words :

  • one is clearly not a word (it begins with #)
  • one is the first word in the list
  • one is the last word in the list
  • one looks like a word but isn't


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

U chiqadi:

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

So it looks like the search for a single word with a '\b(word1|word2|...|wordN)\b' pattern has:

  • O(1) best case
  • O(n/2) average case, which is still O(n)
  • O(n) worst case

Ushbu natijalar oddiy pastadir qidiruvi bilan mos keladi.

Ro'yxatga olish birlashmasiga juda tezroq muqobil trieydan regex naqshni yaratishdir.

90
qo'shib qo'ydi
@ErikDuminil: lite ning eritmasi faqatgina O (n * m) bo'lsa, oddiy ifodalovchi motor so'zning ko'rinishini O (m) ga aylantirsa. Umid qilamanki, uni optimallashtiradi, garchi u qarama-qarshi jadval sifatida samarali bo'lmasligi mumkin.
qo'shib qo'ydi muallif Matthieu M., manba
@ErikDuminil: Katta ish! Men ikkinchi marta urishni istardim.
qo'shib qo'ydi muallif Matthieu M., manba
@ErikDuminil: Afsuski, bu ichki va qiyin hujjatlashtirilgan narsalardir, shuning uchun kodni o'qish yoki kimni so'raganimni so'ramasligimizdan qo'rqaman :(
qo'shib qo'ydi muallif Matthieu M., manba
Siz to'g'ri edingiz. Chertishim noto'g'ri edi. Men uni asl masalada aniqladim. 50 ta jumlaning sekin bo'lganligi haqidagi sharhga kelsak, men faqat soddalashtirilgan misolni taqdim etaman. Haqiqiy ma'lumotlar to'plami men tasvirlab berganimdan ko'ra murakkabroq, lekin u to'g'ri kelmasdi. Bundan tashqari, bitta "regex" da mening "so'zlarim" ni birlashtirmoq tezlikni oshirdi. Bundan tashqari, men almashtirishdan keyin ikkita bo'shliqni "siqib qo'yaman".
qo'shib qo'ydi muallif user36476, manba
@ErikDuminil: juda yaxshi ish.
qo'shib qo'ydi muallif Casimir et Hippolyte, manba
@Mattium. "Umid qilamanki" haqida roziman. Bir muddat qidirib qoldim, masalan, "word1 | word2 | word3" "so'ziga [123]" uchun optimallashtirilganligini bildirmadim.
qo'shib qo'ydi muallif Eric Duminil, manba
@ user36476 Geribildirim uchun rahmat, men tegishli qismini olib tashladim. Iltimos, taklifimni sinab ko'rsata olasizmi? Qabul qilingan javobdan ancha tezroq ekanini aytishga jur'at etaman.
qo'shib qo'ydi muallif Eric Duminil, manba
@ user36476: Fikringizni yangilab ko'rganman va introni o'zgartirdim.
qo'shib qo'ydi muallif Eric Duminil, manba
@idmean: To'g'ri, bu juda aniq emas edi. Bu faqat qidirishga tegishli edi: "Bu so'z taqiqlangan so'zmi?".
qo'shib qo'ydi muallif Eric Duminil, manba
@Mattium. Biz taxmin qilishimiz mumkin. Men o'sib borayotgan regexen bilan bir necha sinov qilaman, va men bu narsalarni tezlashtiradimi yoki yo'qligini bilish uchun ittifoqdan regex trii qurmoqchiman.
qo'shib qo'ydi muallif Eric Duminil, manba
@Mattium. Yangilanishimni ko'ring. Regexp birlashma bo'yicha qidirish O (m) ko'rinishida
qo'shib qo'ydi muallif Eric Duminil, manba
@CasimiretHippolyte: Ishtirokchilar, sizningcha, 2 echim!
qo'shib qo'ydi muallif Eric Duminil, manba
Siz bu noto'g'ri O (1) da'vosini olib tashlaganingiz uchun, javobingiz, albatta, yuqoriroq ovozga loyiqdir.
qo'shib qo'ydi muallif idmean, manba

TLDR

Tezroq regex-asosidagi hal qilishni xohlasangiz, ushbu usuldan foydalaning. O'R ni o'xshash ma'lumotlar bazasi uchun qabul qilingan javobdan taxminan 1000 marta tezroq.

Regex haqida qayg'ursa, ushbu setka asoslangan versiyasidan foydalaning, bu esa regex birlashmasidan 2000 marta tezdir.

Trie bilan optimallashtirilgan Regex

oddiy Regex birlashmasi ko'plab taqiqlangan so'zlar bilan sekinlashadi, chunki regex qidiruvi

Barcha taqiqlangan so'zlar bilan

Trie yaratish va tegishli regex yozish mumkin. Olingan trie yoki regex aslida inson o'qiy olmaydi, lekin ular juda tez qo'ng'iroq qilish va o'yinlarga ruxsat berishadi.

Misol

['foobar', 'foobah', 'fooxar', 'foozap', 'fooza']

Regex union

Ro'yxat, bir trieye aylanadi:

{'f': {'o': {'o': {'x': {'a': {'r': {'': 1}}}, 'b': {'a': {'r': {'': 1}, 'h': {'': 1}}}, 'z': {'a': {'': 1, 'p': {'': 1}}}}}}}

Va keyin bu regex naqsh:

r"\bfoo(?:ba[hr]|xar|zap?)\b"

Regex trie

Katta afzallik shuki, agar zoo bilan mos kelishini tekshirish uchun regex qidiruvi faqat solishtirish kerak 5 so'zni ishlatmoqchi o'rniga birinchi belgi (u mos kelmaydi). . Bu 5 ta so'z uchun oldindan ishlov beriladigan ustundir, ammo u minglab so'zlar uchun umid beruvchi natija beradi.

Eslatma: (?:) bo'lmagan guruhlar ishlatiladi, chunki:

Kod

Here's a slightly modified gist, which we can use as a trie.py library:

import re


class Trie():
    """Regex::Trie in Python. Creates a Trie out of a list of words. The trie can be exported to a Regex pattern.
    The corresponding Regex should match much faster than a simple Regex union."""

    def __init__(self):
        self.data = {}

    def add(self, word):
        ref = self.data
        for char in word:
            ref[char] = char in ref and ref[char] or {}
            ref = ref[char]
        ref[''] = 1

    def dump(self):
        return self.data

    def quote(self, char):
        return re.escape(char)

    def _pattern(self, pData):
        data = pData
        if "" in data and len(data.keys()) == 1:
            return None

        alt = []
        cc = []
        q = 0
        for char in sorted(data.keys()):
            if isinstance(data[char], dict):
                try:
                    recurse = self._pattern(data[char])
                    alt.append(self.quote(char) + recurse)
                except:
                    cc.append(self.quote(char))
            else:
                q = 1
        cconly = not len(alt) > 0

        if len(cc) > 0:
            if len(cc) == 1:
                alt.append(cc[0])
            else:
                alt.append('[' + ''.join(cc) + ']')

        if len(alt) == 1:
            result = alt[0]
        else:
            result = "(?:" + "|".join(alt) + ")"

        if q:
            if cconly:
                result += "?"
            else:
                result = "(?:%s)?" % result
        return result

    def pattern(self):
        return self._pattern(self.dump())

Viktorina

Here's a small Viktorina (the same as this one):

# Encoding: utf-8
import re
import timeit
import random
from trie import Trie

with open('/usr/share/dict/american-english') as wordbook:
    banned_words = [word.strip().lower() for word in wordbook]
    random.shuffle(banned_words)

Viktorina_words = [
    ("Surely not a word", "#surely_NöTäWORD_so_regex_engine_can_return_fast"),
    ("First word", banned_words[0]),
    ("Last word", banned_words[-1]),
    ("Almost a word", "couldbeaword")
]

def trie_regex_from_words(words):
    trie = Trie()
    for word in words:
        trie.add(word)
    return re.compile(r"\b" + trie.pattern() + r"\b", re.IGNORECASE)

def find(word):
    def fun():
        return union.match(word)
    return fun

for exp in range(1, 6):
    print("\nTrieRegex of %d words" % 10**exp)
    union = trie_regex_from_words(banned_words[:10**exp])
    for description, Viktorina_word in Viktorina_words:
        time = timeit.timeit(find(Viktorina_word), number=1000) * 1000
        print("  %s : %.1fms" % (description, time))

U chiqadi:

TrieRegex of 10 words
  Surely not a word : 0.3ms
  First word : 0.4ms
  Last word : 0.5ms
  Almost a word : 0.5ms

TrieRegex of 100 words
  Surely not a word : 0.3ms
  First word : 0.5ms
  Last word : 0.9ms
  Almost a word : 0.6ms

TrieRegex of 1000 words
  Surely not a word : 0.3ms
  First word : 0.7ms
  Last word : 0.9ms
  Almost a word : 1.1ms

TrieRegex of 10000 words
  Surely not a word : 0.1ms
  First word : 1.0ms
  Last word : 1.2ms
  Almost a word : 1.2ms

TrieRegex of 100000 words
  Surely not a word : 0.3ms
  First word : 1.2ms
  Last word : 0.9ms
  Almost a word : 1.6ms

Ma'lumot uchun, regex quyidagi kabi boshlanadi:

(?: A (?: (): \ 's | a (?: \' p | chen | liyah (?: \ 's)? r (?: dvark (?: (?: \ s | s | (a: a (?: us (?: (?: \ 's | es))? | [ik]) | ft | Faqat (?: (?: \ 's | s))? ndon (? :(?: ed | ing | ment (?: \' s) | s))? s (?: E (?: (?: E: ():? [Ds]))? h (? :(?: E [ds] | ing))? | ing | | t (?: E (? :(? : Ment (?: \ 's)? | [ds])) | ing | toir (?: (?: \' s | s))?)) | b (?: as (?: id)? | e (?: ss (?: (?: \ 's | es))? y (?: (?: \' s | s))? | | ot (?: (?: \ 's | t (?: ng | on (?: (?: \ 's |))?)) | y (? : \ 's)? | \ é (?: (?: \' s))?) | d (?: icat (?: E [ds]? | ?: \ 's | s)))) | om (?: eng (?: (?: \' s |)? inal) | u (?: ct (? :( :: ed | (?: ng | on (?: (|: \ 's | s))? | | yoki (?: (?: \' s))? | s))? l (?: \ )? (|: (?: (?: \ 'S | ard | son (?: \' s)))? r (?: deen (?: (?: \ 's)? ra (?: nt | tion (?: (?: \' s))?)) | t (? :( ?: T (?: E (?: R (?: (?: \ 'p | s))? d) | ing | yoki (?: (?: \' s |))?) | s))? yance (?: \ 's)? d))? hor (? :( ?: r (?: E (?: n (?: ce (?: \' s)? | t) | d) | g) | g (|: g | | | | | | | | | | | | | | | | | | | | | | (?: \ 's)?))) | j (?: Ekt (?: ly)? | ur (?: ate (?: (?: \' p | s)) | e [ds]? )) | l (?: A (?: \ 's | s) | ze) | e (? :( ?: st | r)) | oom | ish (? : (?: \ 's | s) (?: ng | on (?: \ 's))] | r (? (?: (|: ():?: | | | | | | | | | | : \ 's | (?: (?: Sh (? :(?: E [ds] | ing))? | | | | | | | | | | | {| | | | | | | | | )?))) | r (?: igin (?: al (?: (?: \ 's |)? | e (?: (?: \' s | s))? | | :( ?: ed | i (?: ng | on (?: (?: \ 's | ist (?: (?: \' s |)? | s))? | va) | s)) |? (?: Nd (? :( ?: ed | ing | s)? |) | va (?: (?: \ 's? board))? r (?: a (? : cadabra (?: \ 's)? D (?: E [ds]? | ing) | ham (?: \' s)? m (?: (?: \ 's | s))? si (?: on (?: (?: \ 's |)? | va (?: (?: \' S | ly | ness (?: \ 's)? | s))?)) | sharqiy | idg (?: E (? :( ?: ment (?: (?: \ 's))? | [ds])) | ing | ment (?: (?: \' s | s )) | o (?: ad | gat (?: E [ds]? | i (?: ng | on (?: (?: \ 's))?))) | upt (: (?: st | r) | ly | ness (?: \ 's)?))) | s (?: alom | c (?: Esh (?: (?: \' s | e [Ds] | ing))? issa (?: (?: \ 's | | es])? ond (? :( ?: ed | ing | s)) | en (?: ce ?: (?: \ 'p | s))? T (? :(?: E (?: E (?: (?: \' s | ism (?: \ 's)? | s))? | | | | | | (?: L (?: ut (?:? e (?: (?: \ 'p | ly | st?)) | i (?: on (?: \' s)? sm (?: \ 's)) | v (?: E [ds]? | ing)) r (?: b (? :(?: E (?: n (?: cy (?: \ 's)? | t (?: (?: \' p | s ))? d) | ing | s))? | pti ...

Bu, albatta, o'qib bo'lmaydigan, lekin 100000 ta taqiqlangan so'zlarning ro'yxati uchun Trie regex oddiy regex birlashmasidan 1000 marta tezdir!

Here's a diagram of the complete trie, exported with trie-python-graphviz and graphviz twopi:

Enter image description here

65
qo'shib qo'ydi
@XavierCombelle: Siz ta'qib qilayotgan guruh haqida gapirishim kerakligini aytdingiz: javob yangilandi. Buning o'rniga men boshqa yo'lni ko'rmoqdaman: parenflar | bilan regeks almashinuvi uchun kerak, lekin bizni qo'lga olish uchun guruhga ehtiyojimiz yo'q. Ular faqatgina jarayonni sekinlashtirmoqdalar va foydani ko'proq xotiradan foydalanadilar.
qo'shib qo'ydi muallif Eric Duminil, manba
@MohamedALANI: Mening zavqim! Kodni yozish va uni juda yaxshi ishlashini ko'rish juda qiziqarli edi. Iltimos, boshqa javobingizni ko'rib chiqing a>, bu ham tezroq.
qo'shib qo'ydi muallif Eric Duminil, manba
@MohamedALANI: Qaysi yechim bilan taqqoslangan?
qo'shib qo'ydi muallif Eric Duminil, manba
Asl maqsadda, qo'lga olinmagan guruhga ehtiyoj yo'qligini anglatadi. Hech bo'lmaganda, qo'lga olinmagan guruhning ma'nosi esga olinishi kerak
qo'shib qo'ydi muallif Xavier Combelle, manba
@ErikDuminil Bu post mukammal, juda minnatdorman :)
qo'shib qo'ydi muallif Mohamed AL ANI, manba
@EricDuminil Rahmat qilgan narsam, barcha so'zlarni ro'yxatdan (200.000 so'z va so'z guruhlari, masalan: ["fil", "qora fil", "sohada yugurish"] topish uchun rahmat./code>) matn misolida paydo bo'ladi. Men sizning trieni bir re.findall (trie.pattern (), matn) bilan ishlatardim, lekin kodimni tezlashtiradigan ko'rinmaydi :( Any idea?
qo'shib qo'ydi muallif Mohamed AL ANI, manba

Sinashni xohlashingiz mumkin bo'lgan bir narsa so'z chegaralarini kodlash uchun jumlalarni oldindan ko'rib chiqish. So'z so'zlari chegarasiga bo'linib, asosan har bir jumlani so'zlar ro'yxatiga o'tkazing.

Bu tezroq bo'lishi kerak, chunki gapni qayta ishlash uchun faqat so'zlarning har birini bosib o'tish va uning o'yin ekanligini tekshirish kerak.

Hozirgi vaqtda regex qidiruvi so'zning har bir qismini qayta ko'rib chiqadi va so'z chegaralarini izlaydi, so'ngra ushbu ishning natijasini keyingi pasdan oldin "olib tashlaydi".

11
qo'shib qo'ydi

Mana, tezkor va qulay echim.

G'olibona kurash strategiyasi:

re.sub ("\ w +", o'zgartirish, jumla) so'zlarni izlaydi.

"O'zgartirish" so'zi chaqirilishi mumkin. Dict qidirishni amalga oshiruvchi funksiya ishlatilgan va dictda qidirish va almashtirish uchun so'zlar mavjud.

Bu eng oddiy va eng tezkor echimdir (qarang: funktsiyaning o'rnini o'zgartirish uchun quyidagi kodni kiriting).

Ikkinchi eng yaxshi

Idea, gaplarni jumlaga o'zgartirib, keyinchalik re.splitni ajratib, keyinchalik ayiruvchilarni jumlalarni qayta tiklashni saqlab qolishdir. Keyinchalik, o'zgartirish oddiy dict qidirish bilan amalga oshiriladi.

(Qarang: funktsiyaning o'rnini o'zgartirish uchun quyidagi kodni kiriting).

Masalan vazifalar vaqtlari:

replace1: 0.62 sentences/s
replace2: 7.43 sentences/s
replace3: 48498.03 sentences/s
replace4: 61374.97 sentences/s (...and 240.000/s with PyPy)

... va kod:

#! /bin/env python3
# -*- coding: utf-8

import time, random, re

def replace1( sentences ):
    for n, sentence in enumerate( sentences ):
        for search, repl in patterns:
            sentence = re.sub( "\\b"+search+"\\b", repl, sentence )

def replace2( sentences ):
    for n, sentence in enumerate( sentences ):
        for search, repl in patterns_comp:
            sentence = re.sub( search, repl, sentence )

def replace3( sentences ):
    pd = patterns_dict.get
    for n, sentence in enumerate( sentences ):
        #~ print( n, sentence )
        # Split the sentence on non-word characters.
        # Note:() in split patterns ensure the non-word characters ARE kept
        # and returned in the result list, so we don't mangle the sentence.
        # If ALL separators are spaces, use string.split instead or something.
        # Example:
        #~ >>> re.split(r"([^\w]+)", "ab céé? . d2eéf")
        #~ ['ab', ' ', 'céé', '? . ', 'd2eéf']
        words = re.split(r"([^\w]+)", sentence)

        # and... done.
        sentence = "".join( pd(w,w) for w in words )

        #~ print( n, sentence )

def replace4( sentences ):
    pd = patterns_dict.get
    def repl(m):
        w = m.group()
        return pd(w,w)

    for n, sentence in enumerate( sentences ):
        sentence = re.sub(r"\w+", repl, sentence)



# Build test set
test_words = [ ("word%d" % _) for _ in range(50000) ]
test_sentences = [ " ".join( random.sample( test_words, 10 )) for _ in range(1000) ]

# Create search and replace patterns
patterns = [ (("word%d" % _), ("repl%d" % _)) for _ in range(20000) ]
patterns_dict = dict( patterns )
patterns_comp = [ (re.compile("\\b"+search+"\\b"), repl) for search, repl in patterns ]


def test( func, num ):
    t = time.time()
    func( test_sentences[:num] )
    print( "%30s: %.02f sentences/s" % (func.__name__, num/(time.time()-t)))

print( "Sentences", len(test_sentences) )
print( "Words    ", len(test_words) )

test( replace1, 1 )
test( replace2, 10 )
test( replace3, 1000 )
test( replace4, 1000 )
7
qo'shib qo'ydi
Sinovlar uchun upvote. replace4 va kodim shu kabi ko'rsatkichlarga ega.
qo'shib qo'ydi muallif Eric Duminil, manba
replace (m): funktsiyasini qanday bajarayotganini va m funktsiyasini sizning funktsiyangizga qanday o'zgartirganligiga ishonch hosil qiling4
qo'shib qo'ydi muallif Enthusiast, manba
Bundan tashqari, xato: muvozanatsiz parantez kodi uchun patterns_comp = [(re.compile ("\\ b" + search + "\\ b"), qidirish uchun naqshde]
qo'shib qo'ydi muallif Enthusiast, manba

Ehtimol Python bu erda to'g'ri vosita emas. Mana Unix asboblar paneli bilan

sed G file         |
tr ' ' '\n'        |
grep -vf blacklist |
awk -v RS= -v OFS=' ' '{$1=$1}1'

Qora ro'yxat dosyaning qo'shilgan so'z chegaralari bilan oldindan ishlov berilishini nazarda tutadi. Qadamlar quyidagilardir: faylni ikki marta intervalgacha aylantirish, har bir jumlani har bir satrda bitta so'zga bo'lish, ommaviy qirg'in ro'yxatini fayldan o'chirish va chiziqlarni birlashtirish.

Bu kamida bir o'lchamdagi buyurtma tezroq ishlashi kerak.

Qora ro'yxat faylini so'zlardan (har bir satrda bitta so'z)

sed 's/.*/\\b&\\b/' words > blacklist
6
qo'shib qo'ydi

Bunga qanday qaraysiz:

#!/usr/bin/env python3

from __future__ import unicode_literals, print_function
import re
import time
import io

def replace_sentences_1(sentences, banned_words):
    # faster on CPython, but does not use \b as the word separator
    # so result is slightly different than replace_sentences_2()
    def filter_sentence(sentence):
        words = WORD_SPLITTER.split(sentence)
        words_iter = iter(words)
        for word in words_iter:
            norm_word = word.lower()
            if norm_word not in banned_words:
                yield word
            yield next(words_iter) # yield the word separator

    WORD_SPLITTER = re.compile(r'(\W+)')
    banned_words = set(banned_words)
    for sentence in sentences:
        yield ''.join(filter_sentence(sentence))


def replace_sentences_2(sentences, banned_words):
    # slower on CPython, uses \b as separator
    def filter_sentence(sentence):
        boundaries = WORD_BOUNDARY.finditer(sentence)
        current_boundary = 0
        while True:
            last_word_boundary, current_boundary = current_boundary, next(boundaries).start()
            yield sentence[last_word_boundary:current_boundary] # yield the separators
            last_word_boundary, current_boundary = current_boundary, next(boundaries).start()
            word = sentence[last_word_boundary:current_boundary]
            norm_word = word.lower()
            if norm_word not in banned_words:
                yield word

    WORD_BOUNDARY = re.compile(r'\b')
    banned_words = set(banned_words)
    for sentence in sentences:
        yield ''.join(filter_sentence(sentence))


corpus = io.open('corpus2.txt').read()
banned_words = [l.lower() for l in open('banned_words.txt').read().splitlines()]
sentences = corpus.split('. ')
output = io.open('output.txt', 'wb')
print('number of sentences:', len(sentences))
start = time.time()
for sentence in replace_sentences_1(sentences, banned_words):
    output.write(sentence.encode('utf-8'))
    output.write(b' .')
print('time:', time.time() - start)

Ushbu echimlar so'z chegaralariga bo'linadi va har bir so'zni to'plamda ko'rib chiqadi. O (n) , bu erda gumon qilingan O (1) tufayli kirishning hajmi O (n) bo'lsa, ular so'zlarning muqobillari (liteyes) parametrlarini tekshirganda, regex o'zgaruvchilarini ishlatganda regex dvigatelining so'z chegaralarida emas, balki har bir belgi bo'yicha so'zlar bilan solishishini tekshirish kerak. Mening yechimim original matnda ishlatilgan (ya'ni, bo'shliqlarni siqib qo'ymaydi, yorliqlarni, yangi satrlarni va boshqa bo'shliq belgilarini saqlamaydi) saqlanadigan bo'sh joylarni saqlab qolish uchun qo'shimcha ehtiyotkorlik bilan ishlaydi, ammo siz bu haqda g'amxo'rlik qilmasligingizga qaror qilsangiz, ularni chiqimdan olib tashlash uchun juda sodda bo'lishi kerak.

Gutenberg loyihasidan tushirilgan bir nechta e-kitoblarning birlashtirilishi bo'lgan corpus.txt ustida testlangan va banned_words.txt Ubuntu so'zlashuv ro'yxatidan (/ usr/share/dict/american-english) tasodifiy tanlangan 20000 so'z. Bu erda taxminan 30 soniya davom etadi, 862462 ta jumla (va ularning yarmi PyPy da). Men gaplarni "." Tomonidan ajratilgan narsa deb ta'rifladim.

$ # replace_sentences_1()
$ python3 filter_words.py 
number of sentences: 862462
time: 24.46173644065857
$ pypy filter_words.py 
number of sentences: 862462
time: 15.9370770454

$ # replace_sentences_2()
$ python3 filter_words.py 
number of sentences: 862462
time: 40.2742919921875
$ pypy filter_words.py 
number of sentences: 862462
time: 13.1190629005

PyPy ayniqsa, ikkinchi yondashuvdan ko'proq foyda ko'radi, ammo CPython birinchi yondashuvda yaxshiroq yo'l tutdi. Yuqoridagi kod Python 2 va 3 da ishlashi kerak.

4
qo'shib qo'ydi
Agar men buni to'g'ri tushunsam, asosan mening javobim bilan bir xil tamoyilga o'xshaydi, lekin undan ko'ra yaxshiroqmi? \ W + ga bo'lingan va qo'shilish asosan \ w + da pastki ga o'xshaydi?
qo'shib qo'ydi muallif Eric Duminil, manba
Quyidagi echim (funktsiya repl4) pypy dan tezroqmi deb o'ylayman;) Fayllaringizni sinab ko'rmoqchiman!
qo'shib qo'ydi muallif peufeu, manba
Python 3 bu savolda berilgan. Buni yuqorilardim, lekin menimcha, ushbu kodda batafsil ma'lumot berish uchun ba'zi tafsilotlarni va "maqbul" dasturni qurbon qilish arziydi.
qo'shib qo'ydi muallif pvg, manba

Amaliy yondashuv

Quyida tasvirlangan echim barcha matnni bir xil satrda saqlash va murakkablik darajasini kamaytirish uchun juda ko'p xotiradan foydalanadi. Agar RAM muammo bo'lsa, uni ishlatishdan oldin ikki marta o'ylab ko'ring.

join / split fokuslari bilan algoritmni tezlashtiradigan looplardan qochishingiz mumkin.

  • Quyidagi jumlalarda bo'lmagan maxsus delimetrli jumlalarni birlashtir:
  • merged_sentences = ' * '.join(sentences)
    
  • | "yoki" regex statement "dan foydalanib, jumlalardan qutilish kerak bo'lgan barcha so'zlar uchun bitta regexni kompilyatsiya qiling:
  • regex = re.compile(r'\b({})\b'.format('|'.join(words)), re.I) # re.I is a case insensitive flag
    
  • Pastki qismdan olingan regeks bilan so'zlarni ajratib oling va alohida ajratuvchi belgidan ajratilgan jumlaga qaytaring:
  • clean_sentences = re.sub(regex, "", merged_sentences).split(' * ')
    

    Ishlash

    "".join complexity is O(n). This is pretty intuitive but anyway there is a shortened quotation from a source:

    for (i = 0; i < seqlen; i++) {
        [...]
        sz += PyUnicode_GET_LENGTH(item);
    

    Shuning uchun join/split bilan boshlang'ich yondashuv bilan siz hali ham lineer murakkablik va 2 * O (N 2 ) bo'lgan O (so'zlar) + 2 * O (jumlalar) mavjud.


    b.t.w. multithreading ishlatmang. GIL har bir operatsiyani blokirovka qiladi, chunki sizning vazifangiz CPU bilan bog'liq bo'lib, GILning ozod bo'lish imkoniyati yo'q, lekin har bir ish zarrachasini bir vaqtning o'zida yuboradi, bu esa qo'shimcha harakatlarga olib keladi va hatto amal qilish muddatini uzaytiradi.

    3
    qo'shib qo'ydi
    .. Yoki regex, allaqachon bir nechta so'zlarni almashtirishda ushbu qismlarni ko'chirish uchun optimallashtirilgan bo'lishi mumkin.
    qo'shib qo'ydi muallif Danny_ds, manba
    .. Ushbu oxirgi yondashuv (so'zlar orasidagi qismlarni ko'chirish/yozish) Erik Duminilning o'rnatilgan qo'ng'iroq bilan birgalikda juda tez bo'lishi mumkin, Ehtimol, hech qanday regex ishlatmasdan. (2)
    qo'shib qo'ydi muallif Danny_ds, manba
    Agar matnli faylda jumlalar (saqlangan) bo'lsa, ular allaqachon yangi satr bilan ajratilgan. Shunday qilib, butun faylni bir katta mag'lubiyat (yoki bufer) sifatida o'qib chiqishi mumkin, so'zlar o'chiriladi va keyin qayta yoziladi (yoki faylni bevosita xotira xaritalash yordamida amalga oshirilishi mumkin). Otoh, bir so'zni olib tashlash uchun, magistralning qolgan qismi bo'shliqni to'ldirish uchun orqaga qaytarilishi kerak, shuning uchun juda katta mag'lubiyatga ega bo'lgan muammodir. Shu bilan bir qatorda, so'zlarni boshqa satr yoki faylga (yangi satrlarni o'z ichiga oladigan) qaytarib olish yoki bu qismlarni mmapped faylga ko'chirish (1).
    qo'shib qo'ydi muallif Danny_ds, manba

    Barcha jumlalarni bitta hujjatga biriktirish. Barcha "yomon" so'zlarni topish uchun Aho-Corasick algoritmini ( bu erda bitta ) har qanday dasturidan foydalaning. Faylni siljiting, har qanday noto'g'ri so'zni almashtiring, ta'qib qilinadigan so'zlarni o'chirib tashlang va h.k.

    0
    qo'shib qo'ydi
    Python
    Python
    372 ishtirokchilar

    Bu guruh python dasturlash tilini muhokama qilish uchun. Iltimos, o'zingizni hurmat qiling va faqat dasturlash bo'yicha yozing. Botlar mavzusini @botlarhaqida guruhida muhokama qling! FAQ: @PyFAQ Offtopic: @python_uz_offtopic

    Python offtopic group !
    Python offtopic group !
    150 ishtirokchilar

    @python_uz gruppasining offtop gruppasi. offtop bo'lsa ham reklama mumkin emas ) Boshqa dasturlash tiliga oid gruppalar @languages_programming