文字列置換のベンチマーク
Python で文章中に含まれる半角記号(;/?:@&+,()[]!"#%'~\`)と半角スペースを全て特定の値(-)に置換したい。
例えば、「エメラルド Tour 2010(初回限定盤) [DVD] 」は「エメラルド-Tour-2010-初回限定盤---DVD-」といった感じ。
str.replace をするのか、re.sub が良いのかと思い Twitter で呟いたら以下のリプライを頂いた。
ということで以下のようなのを書いた。
#!/usr/bin/env python # -*- coding: utf-8 -*- import re from benchmarker import Benchmarker, cmdopt def replace_reserved_chars(value): ret = value.replace(';', '-') \ .replace('/', '-') \ .replace('?', '-') \ .replace(':', '-') \ .replace('@', '-') \ .replace('&', '-') \ .replace('=', '-') \ .replace('+', '-') \ .replace(',', '-') \ .replace('(', '-') \ .replace(')', '-') \ .replace('[', '-') \ .replace(']', '-') \ .replace('!', '-') \ .replace('"', '-') \ .replace('#', '-') \ .replace('%', '-') \ .replace("'", '-') \ .replace('~', '-') \ .replace('\\', '-') \ .replace('`', '-') \ .replace(' ', '-') return ret def replace_reserved_chars_regex(value): pattern = r'[\s+]|;|/|\?|:|@|&|=|\+|,|\(|\)|\[|\]|!|"|#|%|\'|~|\\|`' ret = re.sub(pattern, '-', value) return ret def replace_reserved_chars_regex_compile(value): pattern = r'[\s+]|;|/|\?|:|@|&|=|\+|,|\(|\)|\[|\]|!|"|#|%|\'|~|\\|`' r = re.compile(pattern) ret = r.sub('-', value) return ret cmdopt.parse() with Benchmarker(width=20, loop=100*1000) as bm: for _ in bm.empty(): pass for _ in bm('replace one'): replace_reserved_chars('Hello world!') for _ in bm('regex one'): replace_reserved_chars_regex('Hello world!') for _ in bm('regex compile one'): replace_reserved_chars_regex_compile('Hello world!') with Benchmarker(width=20, loop=100*1000) as bm: for _ in bm('replace all'): replace_reserved_chars(' ;/?:@&+,()[]!"#%\'~\\`') for _ in bm('regex all'): replace_reserved_chars_regex(' ;/?:@&+,()[]!"#%\'~\\`') for _ in bm('regex compile all'): replace_reserved_chars_regex_compile(' ;/?:@&+,()[]!"#%\'~\\`') with Benchmarker(width=20, loop=100*1000) as bm: for _ in bm('replace'): replace_reserved_chars(u'エメラルド Tour 2010(初回限定盤) [DVD]') for _ in bm('regex'): replace_reserved_chars_regex(u'エメラルド Tour 2010(初回限定盤) [DVD]') for _ in bm('regex compile'): replace_reserved_chars_regex_compile(u'エメラルド Tour 2010(初回限定盤) [DVD]') with Benchmarker(width=20, loop=100*1000) as bm: for _ in bm('replace all'): replace_reserved_chars(u'エメラルド-Tour-2010-初回限定盤-DVD') for _ in bm('regex all'): replace_reserved_chars_regex(u'エメラルド-Tour-2010-初回限定盤-DVD') for _ in bm('regex compile all'): replace_reserved_chars_regex_compile(u'エメラルド-Tour-2010-初回限定盤-DVD')
ベンチマークには、Benchmarker を使用。
結果
文字列中に記号が一つだけ。
## benchmarker: release 3.0.1 (for python) ## python platform: darwin [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] ## python version: 2.6.6 ## python executable: /opt/local/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python ## user sys total real (Empty) 0.0100 0.0000 0.0100 0.0137 replace one 0.8600 0.0000 0.8600 0.8775 re one 0.6000 0.0100 0.6100 0.6281 re compile one 0.5900 0.0000 0.5900 0.5970 ## Ranking real re compile one 0.5970 (100.0%) ************************* re one 0.6281 ( 95.0%) ************************ replace one 0.8775 ( 68.0%) ***************** ## Ratio Matrix real [01] [02] [03] [01] re compile one 0.5970 100.0% 105.2% 147.0% [02] re one 0.6281 95.0% 100.0% 139.7% [03] replace one 0.8775 68.0% 71.6% 100.0%
文中に少し含んでいる場合。
## user sys total real (Empty) 0.0100 0.0000 0.0100 0.0147 replace 1.2200 0.0100 1.2300 1.2485 re 1.2600 0.0000 1.2600 1.2723 re compile 1.2500 0.0100 1.2600 1.2768 ## Ranking real replace 1.2485 (100.0%) ************************* re 1.2723 ( 98.1%) ************************* re compile 1.2768 ( 97.8%) ************************ ## Ratio Matrix real [01] [02] [03] [01] replace 1.2485 100.0% 101.9% 102.3% [02] re 1.2723 98.1% 100.0% 100.3% [03] re compile 1.2768 97.8% 99.7% 100.0%
文章が全て置換対象文字だけの場合。
## user sys total real (Empty) 0.0200 0.0000 0.0200 0.0139 replace all 1.0100 0.0000 1.0100 1.0289 re all 1.1600 0.0100 1.1700 1.2312 re compile all 1.1400 0.0000 1.1400 1.1731 ## Ranking real replace all 1.0289 (100.0%) ************************* re compile all 1.1731 ( 87.7%) ********************** re all 1.2312 ( 83.6%) ********************* ## Ratio Matrix real [01] [02] [03] [01] replace all 1.0289 100.0% 114.0% 119.7% [02] re compile all 1.1731 87.7% 100.0% 104.9% [03] re all 1.2312 83.6% 95.3% 100.0%
置換対象が全くない場合。
## user sys total real (Empty) 0.0100 0.0000 0.0100 0.0134 replace none 1.1300 0.0000 1.1300 1.1367 re none 0.7700 0.0000 0.7700 0.7902 re compile none 0.7600 0.0100 0.7700 0.7759 ## Ranking real re compile none 0.7759 (100.0%) ************************* re none 0.7902 ( 98.2%) ************************* replace none 1.1367 ( 68.3%) ***************** ## Ratio Matrix real [01] [02] [03] [01] re compile none 0.7759 100.0% 101.8% 146.5% [02] re none 0.7902 98.2% 100.0% 143.9% [03] replace none 1.1367 68.3% 69.5% 100.0%
記号が 1 つの場合は、re モジュールの方が速い。
記号が少しの場合は、replace の方が速い(ただし他のとは誤差レベル)。
全て記号の場合は replace の方が速い。
全く含まない場合は、re モジュールの方が速い。
正直誤差のような気がする…。
現実的には記号が少しの場合と、全くない場合というユースケースが大半を締めそう。
なので、re.compile を使ってみよう。