-
Notifications
You must be signed in to change notification settings - Fork 290
Regexp.ja
Kouhei Sutou edited this page Apr 18, 2018
·
18 revisions
辞書データを冗長にして異表記を吸収するのにも限界がある。
辞書データを生成する際には以下で述べる正規化処理を全て適用しているため、 解析対象のテキストに対して以下の正規化処理を適用すると、辞書中の語とマッチしやすくなる。
以下にmecab-ipadic-neologd のエントリを生成する際に、処理の各所に分散している正規化処理をまとめる。
生成時には色々置換と削除をしているが、最後に反映されているのは以下である。
- 0-9=> 0-9
- A-Z=> A-Z
- a-z=> a-z
半角の濁音と半濁音の記号が1文字扱いになってるので気をつけること。
以下はハイフンマイナスに置換する。
- MODIFIER LETTER MINUS SIGN(U+02D7)
- ARMENIAN HYPHEN(U+058A)
- ハイフン(U+2010)
- ノンブレーキングハイフン(U+2011)
- フィギュアダッシュ(U+2012)
- エヌダッシュ(U+2013)
- Hyphen bullet(U+2043)
- 上付きマイナス(U+207B)
- 下付きマイナス(U+208B)
- 負符号(U+2212)
以下は全角長音記号に置換する。
- エムダッシュ(U+2014)
- ホリゾンタルバー(U+2015)
- 横細罫線(U+2500)
- 横太罫線(U+2501)
- SMALL HYPHEN-MINUS(U+FE63)
- 全角ハイフンマイナス(U+FF0D)
- 半角長音記号(U+FF70)
連続したら削除する。
- スーーーーーーパーーーーーー => スーパー
以下は削除する。
- 半角チルダ
- チルダ演算子
- INVERTED LAZY S
- 波ダッシュ
- WAVY DASH
- 全角チルダ
- /!”#$%&’()*+,−./:;<>?@[¥]^_`{|}
- 。、・=「」
- 中黒とイコールは人名によく使われるので、半角にするのが難しい。
- カギ括弧はおもに全角が使われる
- ' ' => ' '
- ' ' => ' '
- ' テキストの前' => 'テキストの前'
- 'テキストの後 ' => 'テキストの後'
- 検索 エンジン 自作 入門 を 買い ました !!! => 検索エンジン自作入門を買いました!!!
- Coding the Matrix => Coding the Matrix
- アルゴリズム C => アルゴリズムC
- Algorithm C => Algorithm C
上記の処理をそのまま素直に書くと以下のようになる。
実際はもう少し無駄が無いように工夫したり、必要のない処理については行わなければ良いだろう。
Python (written by hideaki-t & overlast)
# encoding: utf8
from __future__ import unicode_literals
import re
import unicodedata
def unicode_normalize(cls, s):
pt = re.compile('([{}]+)'.format(cls))
def norm(c):
return unicodedata.normalize('NFKC', c) if pt.match(c) else c
s = ''.join(norm(x) for x in re.split(pt, s))
s = re.sub('-', '-', s)
return s
def remove_extra_spaces(s):
s = re.sub('[ ]+', ' ', s)
blocks = ''.join(('\u4E00-\u9FFF', # CJK UNIFIED IDEOGRAPHS
'\u3040-\u309F', # HIRAGANA
'\u30A0-\u30FF', # KATAKANA
'\u3000-\u303F', # CJK SYMBOLS AND PUNCTUATION
'\uFF00-\uFFEF' # HALFWIDTH AND FULLWIDTH FORMS
))
basic_latin = '\u0000-\u007F'
def remove_space_between(cls1, cls2, s):
p = re.compile('([{}]) ([{}])'.format(cls1, cls2))
while p.search(s):
s = p.sub(r'\1\2', s)
return s
s = remove_space_between(blocks, blocks, s)
s = remove_space_between(blocks, basic_latin, s)
s = remove_space_between(basic_latin, blocks, s)
return s
def normalize_neologd(s):
s = s.strip()
s = unicode_normalize('0-9A-Za-z。-゚', s)
def maketrans(f, t):
return {ord(x): ord(y) for x, y in zip(f, t)}
s = re.sub('[˗֊‐‑‒–⁃⁻₋−]+', '-', s) # normalize hyphens
s = re.sub('[﹣-ー—―─━ー]+', 'ー', s) # normalize choonpus
s = re.sub('[~∼∾〜〰~]', '', s) # remove tildes
s = s.translate(
maketrans('!"#$%&\'()*+,-./:;<=>?@[¥]^_`{|}~。、・「」',
'!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」'))
s = remove_extra_spaces(s)
s = unicode_normalize('!”#$%&’()*+,-./:;<>?@[¥]^_`{|}〜', s) # keep =,・,「,」
s = re.sub('[’]', '\'', s)
s = re.sub('[”]', '"', s)
return s
if __name__ == "__main__":
assert "0123456789" == normalize_neologd("0123456789")
assert "ABCDEFGHIJKLMNOPQRSTUVWXYZ" == normalize_neologd("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
assert "abcdefghijklmnopqrstuvwxyz" == normalize_neologd("abcdefghijklmnopqrstuvwxyz")
assert "!\"#$%&'()*+,-./:;<>?@[¥]^_`{|}" == normalize_neologd("!”#$%&’()*+,-./:;<>?@[¥]^_`{|}")
assert "=。、・「」" == normalize_neologd("=。、・「」")
assert "ハンカク" == normalize_neologd("ハンカク")
assert "o-o" == normalize_neologd("o₋o")
assert "majikaー" == normalize_neologd("majika━")
assert "わい" == normalize_neologd("わ〰い")
assert "スーパー" == normalize_neologd("スーパーーーー")
assert "!#" == normalize_neologd("!#")
assert "ゼンカクスペース" == normalize_neologd("ゼンカク スペース")
assert "おお" == normalize_neologd("お お")
assert "おお" == normalize_neologd(" おお")
assert "おお" == normalize_neologd("おお ")
assert "検索エンジン自作入門を買いました!!!" == \
normalize_neologd("検索 エンジン 自作 入門 を 買い ました!!!")
assert "アルゴリズムC" == normalize_neologd("アルゴリズム C")
assert "PRML副読本" == normalize_neologd(" PRML 副 読 本 ")
assert "Coding the Matrix" == normalize_neologd("Coding the Matrix")
assert "南アルプスの天然水Sparking Lemonレモン一絞り" == \
normalize_neologd("南アルプスの 天然水 Sparking Lemon レモン一絞り")
assert "南アルプスの天然水-Sparking*Lemon+レモン一絞り" == \
normalize_neologd("南アルプスの 天然水- Sparking* Lemon+ レモン一絞り")
Ruby (written by kimoto and overlast)
以下のコード例では日本語の文字種変換に Moji モジュールを使用している。
インストール方法は以下の通り。
$ sudo gem install moji
詳しくは以下をお読み頂きたい。
http://gimite.net/gimite/rubymess/moji.html
require 'moji'
def normalize_neologd(norm)
norm.tr!("0-9A-Za-z", "0-9A-Za-z")
norm = Moji.han_to_zen(norm, Moji::HAN_KATA)
hypon_reg = /(?:˗|֊|‐|‑|‒|–|⁃|⁻|₋|−)/
norm.gsub!(hypon_reg, "-")
choon_reg = /(?:﹣|-|ー|—|―|─|━)/
norm.gsub!(choon_reg, "ー")
chil_reg = /(?:~|∼|∾|〜|〰|~)/
norm.gsub!(chil_reg, '')
norm.gsub!(/[ー]+/, "ー")
norm.tr!(%q{!"#$%&'()*+,-.\/:;<=>?@[¥]^_`{|}~。、・「」"}, %q{!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」})
norm.gsub!(/ /, " ")
norm.gsub!(/ {1,}/, " ")
norm.gsub!(/^[ ]+(.+?)$/, "\\1")
norm.gsub!(/^(.+?)[ ]+$/, "\\1")
while norm =~ %r{([\p{InCjkUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)[ ]{1}([\p{InCjkUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)}
norm.gsub!( %r{([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)}, "\\1\\2")
end
while norm =~ %r{([\p{InBasicLatin}]+)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)}
norm.gsub!(%r{([\p{InBasicLatin}]+)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)}, "\\1\\2")
end
while norm =~ %r{([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)[ ]{1}([\p{InBasicLatin}]+)}
norm.gsub!(%r{([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)[ ]{1}([\p{InBasicLatin}]+)}, "\\1\\2")
end
norm.tr!(
%q{!”#$%&’()*+,-./:;<>?@[¥]^_`{|}〜},
%q{!"#$%&'()*+,-.\/:;<>?@[¥]^_`{|}~}
)
norm
end
if $0 == __FILE__
def assert(expect, actual)
if expect == actual
true
else
raise "Failed: Want #{expect.inspect} but #{actual.inspect}"
end
end
assert "0123456789", normalize_neologd("0123456789")
assert "ABCDEFGHIJKLMNOPQRSTUVWXYZ", normalize_neologd("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
assert "abcdefghijklmnopqrstuvwxyz", normalize_neologd("abcdefghijklmnopqrstuvwxyz")
assert "!\"\#$\%&'()*+,-./:;<>?@[¥]^_`{|}", normalize_neologd("!”#$%&’()*+,−./:;<>?@[¥]^_`{|}")
assert "=。、・「」", normalize_neologd("=。、・「」")
assert "ハンカク", normalize_neologd("ハンカク")
assert "o-o", normalize_neologd("o₋o")
assert "majikaー", normalize_neologd("majika━")
assert "わい", normalize_neologd("わ〰い")
assert "スーパー", normalize_neologd("スーパーーーー")
assert "!#", normalize_neologd("!#")
assert "ゼンカクスペース", normalize_neologd("ゼンカク スペース")
assert "おお", normalize_neologd("お お")
assert "おお", normalize_neologd(" おお")
assert "おお", normalize_neologd("おお ")
assert "検索エンジン自作入門を買いました!!!", normalize_neologd("検索 エンジン 自作 入門 を 買い ました!!!")
assert "アルゴリズムC", normalize_neologd("アルゴリズム C")
assert "PRML副読本", normalize_neologd(" PRML 副 読 本 ")
assert "Coding the Matrix", normalize_neologd("Coding the Matrix")
assert "南アルプスの天然水Sparking Lemonレモン一絞り", normalize_neologd("南アルプスの 天然水 Sparking Lemon レモン一絞り")
assert "南アルプスの天然水-Sparking*Lemon+レモン一絞り", normalize_neologd("南アルプスの 天然水- Sparking* Lemon+ レモン一絞り")
end
コード例では、Lingua::JA::Regular::Unicode という CPAN モジュールを「半角カタカナ => 全角カタカナ」の変換に使っている。
以下を参考にして、cpanm をインストールする。
http://perldoc.jp/docs/modules/App-cpanminus
その後、以下のコマンドで Lingua::JA::Regular::Unicode をインストールする。
cpanm Lingua::JA::Regular::Unicode
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Encode;
use Lingua::JA::Regular::Unicode;
sub _normalize_str {
my ($str) = @_;
my $norm = "";
if ((defined $str) && ($str ne "")) {
$norm = Encode::decode_utf8($str);
$norm =~ tr/0-9A-Za-z/0-9A-Za-z/;
$norm = Lingua::JA::Regular::Unicode::katakana_h2z($norm);
my $hypon_reg = '(?:˗|֊|‐|‑|‒|–|⁃|⁻|₋|−)';
$norm =~ s|$hypon_reg|-|g;
my $choon_reg = '(?:﹣|-|ー|—|―|─|━)';
$norm =~ s|$choon_reg|ー|g;
my $chil_reg = '(?:~|∼|∾|〜|〰|~)';
$norm =~ s|$chil_reg||g;
$norm =~ s|[ー]+|ー|g;
$norm =~ tr/!"#$%&'()*+,-.\/:;<=>?@[¥]^_`{|}~。、・「」/!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」/;
$norm =~ s| | |g;
$norm =~ s| {1,}| |g;
$norm =~ s|^[ ]+(.+?)$|$1|g;
$norm =~ s|^(.+?)[ ]+$|$1|g;
while ($norm =~ m|([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)|) {
$norm =~ s|([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+?)|$1$2|g;
}
while ($norm =~ m|([\p{InBasicLatin}]+)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)|) {
$norm =~ s|([\p{InBasicLatin}]+)[ ]{1}([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)|$1$2|g;
}
while ($norm =~ m|([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)[ ]{1}([\p{InBasicLatin}]+)|) {
$norm =~ s|([\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}\p{InCJKSymbolsAndPunctuation}]+)[ ]{1}([\p{InBasicLatin}]+)|$1$2|g;
}
$norm =~ tr/!”#$%&’()*+,-./:;<>?@[¥]^_`{|}/!"#$%&'()*+,-.\/:;<>?@[¥]^_`{|}/;
}
return $norm;
}
my $test_str = $ARGV[0];
print Encode::encode_utf8(_normalize_str($test_str))."\n";
$ ./test.pl " PRML 副 読 本 "
PRML副読本
$./test.pl "Coding the Matrix"
Coding the Matrix
$./test.pl "南アルプスの 天然水- Sparking* Lemon+ レモン一絞り"
南アルプスの天然水-Sparking*Lemon+レモン一絞り