メタキャラクタとORの扱い

import sys, re, csv
path_dict, path_data = sys.argv[1], sys.argv[2]

encoding = 'cp932'
col_keyword, col_data = 1, 3

reader = csv.reader(file(path_dict, "rb"))
keyword = [unicode(row[col_keyword], encoding) for row in reader]
pattern = re.compile(u"(" + u"|".join(keyword) + u")")

for row in csv.reader(file(path_data, "rb")):
    print pattern.sub(r"(\1)", unicode(row[col_data], encoding)).encode(encoding)

sys.argv の長さをチェックしていないとか、csv モジュールは ascii の範囲外での動作が保証されていないとか、encoding は locale.getpreferredencoding() でとったほうがいいんじゃないかとか、いろいろ細かいことはあるんだけど、正規表現の生成がまずすぎる。10人以上もブックマークしておいて、だれも気づかんかなぁ。どっちかというと誰もツッコミを入れない点にびっくりだ。
さて、一応問題の解説。

pattern = re.compile(u"(" + u"|".join(keyword) + "u"))

ってやっているけど、これじゃ keyword*1 に "." とか "?" とかのメタキャラクタを含む文字がきたらひどいことになる。単純文字列マッチングなら

pattern = re.compile(u"(" + u"|".join(re.escape(k) for k in keyword) + u")")

とするべき*2
あと、

試しに1500語の辞書を使って2000行くらいのcsvファイルのキーワードを強調してみたところ、あっというまでしたですよ。正規表現なんだからそりゃそうだよな。。。

とのことだが、OR でつないだ正規表現は多分 NFA*3 になって、単純に各パターンを順番に見ていく羽目になる。つまりマッチング毎にキーワード数に比例した計算コストがかかっているのであって、あっという間というのは単に計算量が非常に小さいだけのことだ。
まぁ、こんなことをここで言うまでも無く、そういう話はGoogleの工藤さんがだいぶ前に言及しているので、そっちを参照。(ってもうほとんど書いてしまっているけど)
で、実際どのくらいスケールしないかは良くわからないので、先日抽出したはてなキーワードを使って試してみた。(ちなみに、UnicodeError を無視しているのは Unicode に変換できない文字を含むキーワードがあるから。EUC-JPとしてもおかしいのもある)。

#!/usr/bin/env python

import sys, re, codecs, locale

DOC_ENCODING = locale.getpreferredencoding()
KEYWORD_ENCODING = "euc-jp"

if len(sys.argv) != 4:
    print >>sys.stderr, "usage: %s keywordlist.extracted n doc" % sys.argv[0]
    sys.exit(0)

keyword_path = sys.argv[1]
n = int(sys.argv[2])
doc_path = sys.argv[3]

keywords = []
decoder = codecs.getdecoder(KEYWORD_ENCODING)

for line in open(keyword_path, 'r'):
    try:
        keywords.append(decoder(line)[0].rstrip())
    except UnicodeError:
        pass

keywords = keywords[:n]
pat = re.compile(u'(' + '|'.join(re.escape(k) for k in keywords) + u')')

for line in (x.rstrip() for x in codecs.open(doc_path, 'r', DOC_ENCODING)):
    print pat.sub(r'(\1)', line)

どうなるかといえば入力テキストにもよるのだが、大体 n = 10000 も入れれば

RuntimeError: internal error in regular expression engine

とか言われる。

*1:どうでもいいけど何で単数形?

*2:最初にアップした記事では re.escape が re.compile になってました。スミマセン。

*3:Nondeterministic Finite Automata: 非決定性オートマトン