2013-12-11 11 views
20

Ich versuche, Interpunktion aus einer Unicode-Zeichenfolge effizient zu entfernen. Bei einer normalen Zeichenfolge ist mystring.translate(None, string.punctuation) eindeutig die fastest approach. Dieser Code bricht jedoch eine Unicode-Zeichenfolge in Python 2.7. Wie die Kommentare zu dieser erklären, kann die translate-Methode noch implementiert werden, aber es muss mit einem Dictionary implementiert werden. Wenn ich aber diese implementation benutze, finde ich, dass die Leistung von translate drastisch reduziert ist. Hier ist mein Timing-Code (kopiert in erster Linie von dieser answer):Schnellste Möglichkeit, Interpunktion von einer Unicode-Zeichenfolge in Python zu entfernen

import re, string, timeit 
import unicodedata 
import sys 


#String from this article www.wired.com/design/2013/12/find-the-best-of-reddit-with-this-interactive-map/ 

s = "For me, Reddit brings to mind Obi Wan’s enduring description of the Mos Eisley cantina: a wretched hive of scum and villainy. But, you know, one you still kinda want to hang out in occasionally. The thing is, though, Reddit isn’t some obscure dive bar in a remote corner of the universe—it’s a huge watering hole at the very center of it. The site had some 400 million unique visitors in 2012. They can’t all be Greedos. So maybe my problem is just that I’ve never been able to find the places where the decent people hang out." 
su = u"For me, Reddit brings to mind Obi Wan’s enduring description of the Mos Eisley cantina: a wretched hive of scum and villainy. But, you know, one you still kinda want to hang out in occasionally. The thing is, though, Reddit isn’t some obscure dive bar in a remote corner of the universe—it’s a huge watering hole at the very center of it. The site had some 400 million unique visitors in 2012. They can’t all be Greedos. So maybe my problem is just that I’ve never been able to find the places where the decent people hang out." 


exclude = set(string.punctuation) 
regex = re.compile('[%s]' % re.escape(string.punctuation)) 

def test_set(s): 
    return ''.join(ch for ch in s if ch not in exclude) 

def test_re(s): # From Vinko's solution, with fix. 
    return regex.sub('', s) 

def test_trans(s): 
    return s.translate(None, string.punctuation) 

tbl = dict.fromkeys(i for i in xrange(sys.maxunicode) 
         if unicodedata.category(unichr(i)).startswith('P')) 

def test_trans_unicode(su): 
    return su.translate(tbl) 

def test_repl(s): # From S.Lott's solution 
    for c in string.punctuation: 
     s=s.replace(c,"") 
    return s 

print "sets  :",timeit.Timer('f(s)', 'from __main__ import s,test_set as f').timeit(1000000) 
print "regex  :",timeit.Timer('f(s)', 'from __main__ import s,test_re as f').timeit(1000000) 
print "translate :",timeit.Timer('f(s)', 'from __main__ import s,test_trans as f').timeit(1000000) 
print "replace :",timeit.Timer('f(s)', 'from __main__ import s,test_repl as f').timeit(1000000) 

print "sets (unicode)  :",timeit.Timer('f(su)', 'from __main__ import su,test_set as f').timeit(1000000) 
print "regex (unicode)  :",timeit.Timer('f(su)', 'from __main__ import su,test_re as f').timeit(1000000) 
print "translate (unicode) :",timeit.Timer('f(su)', 'from __main__ import su,test_trans_unicode as f').timeit(1000000) 
print "replace (unicode) :",timeit.Timer('f(su)', 'from __main__ import su,test_repl as f').timeit(1000000) 

Als meine Ergebnisse zeigen, die Unicode-Implementierung übersetzen führt schrecklich:

sets  : 38.323941946 
regex  : 6.7729549408 
translate : 1.27428412437 
replace : 5.54967689514 

sets (unicode)  : 43.6268708706 
regex (unicode)  : 7.32343912125 
translate (unicode) : 54.0041439533 
replace (unicode) : 17.4450061321 

Meine Frage ist, ob es einen schnelleren Weg zu Implementieren Sie translate für Unicode (oder eine andere Methode), die Regex übertreffen würde.

+0

einen kurzen Blick auf den C-Quellcode unter der ' translate "eingebaut zwischen' stringobject.c' und 'unicodeobject.c' sind sie tatsächlich sehr unterschiedlich implementiert. – qwwqwwq

+0

Sie könnten es wahrscheinlich beschleunigen, ja. Es gibt eine Reihe von Funktionsaufrufen, die hauptsächlich der Übersichtlichkeit dienen. diese könnten inline sein. Das Hauptproblem bei der Implementierung ist, dass Unicode-Zeichen intensiver zu analysieren sind und dass die Anzahl der möglichen Ersetzungen viel größer ist (Ihr 'tbl' enthält 585 Zeichen), was die in" unicodeobject "verwendete Kartenstrategie erfordert. Ist die Regex-Methode zu langsam? – beerbajay

+0

Ich habe nicht einmal über die C-Implementierung nachgedacht, ich meinte nur, dass es existierenden Python-Code gibt, der die Regex-Methode übertreffen kann. Die Regex-Methode wird gut funktionieren, das ist, was ich für jetzt implementiert habe, aber es ist fünf mal langsamer und ich habe viel Text, also dachte ich, ich würde fragen. – Michael

Antwort

6

Das aktuelle Testskript ist fehlerhaft, da es nicht mit Gleichem vergleichbar ist.

Für einen gerechteren Vergleich müssen alle Funktionen mit demselben Interpunktionszeichensatz ausgeführt werden (d. H. Entweder alle ASCII oder alle Unicode).

Wenn das getan wird, die Regex und ersetzen Methoden viel schlimmer mit dem vollständigen Satz von Unicode Interpunktionszeichen.

Für vollständige Unicode sieht es aus wie die "set" -Methode ist die beste. Wenn Sie jedoch nur die ASCII-Interpunktionszeichen aus Unicode-Zeichenfolgen entfernen möchten, ist es möglicherweise am besten, sie zu codieren, zu übersetzen und zu dekodieren (abhängig von der Länge der Eingabezeichenfolge).

Die "replace" -Methode kann auch wesentlich verbessert werden, indem vor dem Austausch ein Containment-Test durchgeführt wird (abhängig von der genauen Beschaffenheit der Saite).

Hier einige Beispiele für Ergebnisse aus einer Re-Hash-Wert des Testskript:

$ python2 test.py 
running ascii punctuation test... 
using byte strings... 

set: 0.862006902695 
re: 0.17484498024 
trans: 0.0207080841064 
enc_trans: 0.0206489562988 
repl: 0.157525062561 
in_repl: 0.213351011276 

$ python2 test.py a 
running ascii punctuation test... 
using unicode strings... 

set: 0.927773952484 
re: 0.18892288208 
trans: 1.58275294304 
enc_trans: 0.0794939994812 
repl: 0.413739919662 
in_repl: 0.249747991562 

python2 test.py u 
running unicode punctuation test... 
using unicode strings... 

set: 0.978360176086 
re: 7.97941994667 
trans: 1.72471117973 
enc_trans: 0.0784001350403 
repl: 7.05612301826 
in_repl: 3.66821289062 

Und hier ist die Re-Hash-Skript:

# -*- coding: utf-8 -*- 

import re, string, timeit 
import unicodedata 
import sys 


#String from this article www.wired.com/design/2013/12/find-the-best-of-reddit-with-this-interactive-map/ 

s = """For me, Reddit brings to mind Obi Wan’s enduring description of the Mos 
Eisley cantina: a wretched hive of scum and villainy. But, you know, one you 
still kinda want to hang out in occasionally. The thing is, though, Reddit 
isn’t some obscure dive bar in a remote corner of the universe—it’s a huge 
watering hole at the very center of it. The site had some 400 million unique 
visitors in 2012. They can’t all be Greedos. So maybe my problem is just that 
I’ve never been able to find the places where the decent people hang out.""" 

su = u"""For me, Reddit brings to mind Obi Wan’s enduring description of the 
Mos Eisley cantina: a wretched hive of scum and villainy. But, you know, one 
you still kinda want to hang out in occasionally. The thing is, though, 
Reddit isn’t some obscure dive bar in a remote corner of the universe—it’s a 
huge watering hole at the very center of it. The site had some 400 million 
unique visitors in 2012. They can’t all be Greedos. So maybe my problem is 
just that I’ve never been able to find the places where the decent people 
hang out.""" 

def test_trans(s): 
    return s.translate(tbl) 

def test_enc_trans(s): 
    s = s.encode('utf-8').translate(None, string.punctuation) 
    return s.decode('utf-8') 

def test_set(s): # with list comprehension fix 
    return ''.join([ch for ch in s if ch not in exclude]) 

def test_re(s): # From Vinko's solution, with fix. 
    return regex.sub('', s) 

def test_repl(s): # From S.Lott's solution 
    for c in punc: 
     s = s.replace(c, "") 
    return s 

def test_in_repl(s): # From S.Lott's solution, with fix 
    for c in punc: 
     if c in s: 
      s = s.replace(c, "") 
    return s 

txt = 'su' 
ptn = u'[%s]' 

if 'u' in sys.argv[1:]: 
    print 'running unicode punctuation test...' 
    print 'using unicode strings...' 
    punc = u'' 
    tbl = {} 
    for i in xrange(sys.maxunicode): 
     char = unichr(i) 
     if unicodedata.category(char).startswith('P'): 
      tbl[i] = None 
      punc += char 
else: 
    print 'running ascii punctuation test...' 
    punc = string.punctuation 
    if 'a' in sys.argv[1:]: 
     print 'using unicode strings...' 
     punc = punc.decode() 
     tbl = {ord(ch):None for ch in punc} 
    else: 
     print 'using byte strings...' 
     txt = 's' 
     ptn = '[%s]' 
     def test_trans(s): 
      return s.translate(None, punc) 
     test_enc_trans = test_trans 

exclude = set(punc) 
regex = re.compile(ptn % re.escape(punc)) 

def time_func(func, n=10000): 
    timer = timeit.Timer(
     'func(%s)' % txt, 
     'from __main__ import %s, test_%s as func' % (txt, func)) 
    print '%s: %s' % (func, timer.timeit(n)) 

print 
time_func('set') 
time_func('re') 
time_func('trans') 
time_func('enc_trans') 
time_func('repl') 
time_func('in_repl') 
+0

+1 @Ekhumoro, ich kann nicht schlafen :-) Ich habe über Ihre Kommentare nachgedacht und Sie haben Recht, die Frage ist falsch entwickelt, Ihre Antwort ist die richtige (IMHO), also habe ich die Mine entfernt. – Roberto

+0

@ RobertoSánchez. Vielen Dank! Und hoffe es gibt dir keine Albträume ;-) – ekhumoro

Verwandte Themen