TinySegmenter for c++(cpp)

TinySegmenter という javascript だけで作られたコンパクトな分かち書きソフトウェアをC++に移植するプロジェクトを作ってみるテスト。

とりあえず、 IRegExp の正規表現を使った windows 版をリリース。
http://code.google.com/p/tinysegmenter-cpp/

Linuxの日本語入った正規表現ってどうやるの?
誰か教えてください。

文字列比較高速化とLinux対応

文字列比較を高速にしました、ついでにLinux(gcc4)対応しました。
http://code.google.com/p/tinysegmenter-cpp/

最初、こんなマクロを作って実行前に展開されて定数になってくれたらいいなと思っていたんですけど、

#define STRING_TO_ULONG(p) \
	( \
		(*(((unsigned char*)p) + 1) == 0) ? *((unsigned char*)p) : \
		( \
			(*(((unsigned char*)p) + 2) == 0) ? ((*(((unsigned char*)p) + 0)) << 8) + ((*(((unsigned char*)p) + 1)) ) : \
			( \
				(*(((unsigned char*)p) + 3) == 0) ? ((*(((unsigned char*)p) + 0)) << 16) + ((*(((unsigned char*)p) + 1)) << 8) + ((*(((unsigned char*)p) + 2)) ) : \
				( \
					((*(((unsigned char*)p) + 0)) << 24) + ((*(((unsigned char*)p) + 1)) << 16) + \
						((*(((unsigned char*)p) + 2)) << 8) + ((*(((unsigned char*)p) + 3)) ) \
				) \
			) \
		) \
	)

これではうまくいかないことが分かった。
マクロ展開しても、 ? で分岐があると事前展開されない、、、まー当たり前か。分岐してみないとどっちに行くかわからないしね。
コンパイラの最適化とかで多少は早くなるんだろうけど、見た目もそうとうアレなので、没にした。

そして、いろいろ悩んだ末に、unsigned long r = 'あ' ってやると、0x82A0 と帰ってくることがわかった。
なんだ、何もしなくていいぢゃないか。

twitter でつぶやいたら、コンパイラによっては unsigned long r = 'あ' が 0x82A0 ではなく、0xA082 と逆になって帰ってくるのもあるよって指摘を受けた。

うーん。
とりあえず、VC++gcc で動けばいいかなと思っている。

VC++gcc でやったところ unsigned long r = 'あ' は 0x82A0 と帰ってきたから、とりあえずコレでいいかなと思ってる。


それと、gcc は unsigned long r = 'あ' のところに warning: multi-character character constant 警告が出る。
これは、 -Wno-multichar オプションで抑制可能。

とりあえず、このオプションをつけてコンパイルする。
VC++ の pragma みたいな奴でエラーが抑制できればいいんだけど、 gcc ではできないようなので、 -Wno-multichar をつけて使ってね。

あと、はまったのが (unsigned long)'ア' が 0FFFFFFB1h とマイナスとして解釈されたところ。
twitter でつぶやいたところ、(unsigned long)(unsigned char)'ア' にすればOKといわれた。

(unsigned long)(unsigned char)'ア' にしたところ 0B1h と正しく変換できた。
なんというか、twitter すげー。。。

んなわけで、正規表現に相当するルーチンはこんな感じになった。

std::string TinySegmenter::ctype_(const std::string & str) const
{
	unsigned long c = stringToULong(str.c_str());	//utf8があるとイヤなんで unsigned longで.

//	this->Pattern[Pattern_M].CreatePattern("[一二三四五六七八九十百千万億兆]");
	unsigned long Pattern_MArray[] = { '一','二','三','四','五','六','七','八','九'
					,'十','百','千','万','億','兆' , 0};
	for (unsigned long * Pattern_MArrayP = Pattern_MArray ; *Pattern_MArrayP ; Pattern_MArrayP++ )
	{
		if (c == *Pattern_MArrayP)
		{
			return "M";
		}
	}

	//	this->Pattern[Pattern_H].CreatePattern("[一-龠々〆ヵヶ]");
	if (c >= (unsigned long)'一' && c <= (unsigned long)'龠')
	{
		return "H";
	}
	unsigned long Pattern_HArray[] = { '々','〆','ヵ','ヶ', 0 };
	for (unsigned long * Pattern_HArrayP = Pattern_HArray ; *Pattern_HArrayP ; Pattern_HArrayP++ )
	{
		if (c == *Pattern_HArrayP)
		{
			return "H";
		}
	}

	//	this->Pattern[Pattern_I].CreatePattern("[ぁ-ん]");
	if (c >= (unsigned long)'ぁ' && c <= (unsigned long)'ん')
	{
		return "I";
	}

	//	this->Pattern[Pattern_K].CreatePattern("[ァ-ヴーア-ン゙ー]");
	if ((c >= ((unsigned long)'ァ') && c <= ((unsigned long)'ヴ') ) || 
		(c >= ((unsigned long)(unsigned char)'ア')  && c <= ((unsigned long)(unsigned char)'ン') ) || 
		(c == ((unsigned long)'ー') || c == ((unsigned long)(unsigned char)'゙') || c == (unsigned long)(unsigned char)'ー' ) )
	{
		return "K";
	}

	//	this->Pattern[Pattern_A].CreatePattern("[a-zA-Za-zA-Z]");
	if ((c >= 'a' && c <= 'z') || 
		(c >= 'A' && c <= 'Z') || 
		(c >= (unsigned long)'a' && c <= (unsigned long)'z') || 
		(c >= (unsigned long)'A' && c <= (unsigned long)'Z')  )
	{
		return "A";
	}

//	this->Pattern[Pattern_N].CreatePattern("[0-90-9]");
	if ((c >= '0' && c <= '9') || 
		(c >= (unsigned long)'0' && c <= (unsigned long)'9')  )
	{
		return "N";
	}

	return "O";
}

色々修正 ver 0.35

sjisの漢字コードの開始と終端の指定が甘かったので修正しました。

あと、オリジナルでは数字のパースが少し変だったので修正しました。
オリジナル: 256 → 2 | 5 | 6
修正版 256 → 256

小数点も数字として認識されなかったので修正しました。
オリジナル: 1.5 → 1 | . | 5
修正版 1.5 → 1.5

(ピリオドへの誤爆はほぼ発生しないと思います。。。)
また、「。」があっても分割されない問題があったので修正しました。
オリジナル: です。わずか → です | 。わずか
修正版 です。わずか → です | 。| わずか

これら3つの修正は #define ENABLE_ENGINE_FIX が有効なときだけ作用します。とりあえずディフォルトで有効にしてありますが、オリジナルのパースルールを使いたいときはコメントアウトしてください。

正規表現が不要になりました。

TinySegmenter for c++(cpp) が正規表現なくても動くようになりました。
http://code.google.com/p/tinysegmenter-cpp/

現在 sjis 依存ですが、正規表現なしで動きます。
TinySegmenter自体がそもそもあんまり正規表現を使っていなかったので、数字演算に置き換えました。

正規表現の代わりをしてくれるのはこんなルーチン。

//文字を unsigned long にする. 将来的にマクロ展開にしたい。
unsigned long TinySegmenter::stringToULong(const char * p) const
{
	ASSERT(*p != NULL);

	if (*(p + 1) == 0)
	{
		return *(unsigned char*)p;
	}
	return ((*(unsigned char*)(p + 0)) << 8) + ((*(unsigned char*)(p + 1)) );
/*
UTF-8等のエンコードを使う人はこっち
	if (*(p + 2) == 0)
	{
		return ((*(unsigned char*)(p + 0)) << 8) + ((*(unsigned char*)(p + 1)) );
	}
	if (*(p + 3) == 0)
	{
		return ((*(unsigned char*)(p + 0)) << 16) + ((*(unsigned char*)(p + 1)) << 8) + ((*(unsigned char*)(p + 2)) ) ;
	}
	return ((*(unsigned char*)(p + 0)) << 24) + ((*(unsigned char*)(p + 1)) << 16) + 
					((*(unsigned char*)(p + 2)) << 8) + ((*(unsigned char*)(p + 3)) );
*/
}

std::string TinySegmenter::ctype_(const std::string & str) const
{
	unsigned long c = stringToULong(str.c_str());	//utf8があるとイヤなんで unsigned longで.

//	this->Pattern[Pattern_M].CreatePattern("[一二三四五六七八九十百千万億兆]");
	unsigned long Pattern_MArray[] = { stringToULong("一"),stringToULong("二"),stringToULong("三"),stringToULong("四")
					,stringToULong("五"),stringToULong("六"),stringToULong("七"),stringToULong("八"),stringToULong("九")
					,stringToULong("十"),stringToULong("百"),stringToULong("千"),stringToULong("万"),stringToULong("億")
					,stringToULong("兆") , 0};
	for (unsigned long * Pattern_MArrayP = Pattern_MArray ; *Pattern_MArrayP ; Pattern_MArrayP++ )
	{
		if (c == *Pattern_MArrayP)
		{
			return "M";
		}
	}

	//	this->Pattern[Pattern_H].CreatePattern("[一-龠々〆ヵヶ]");
	if (c >= stringToULong("一") && c <= stringToULong("龠"))
	{
		return "H";
	}
	unsigned long Pattern_HArray[] = { stringToULong("々"),stringToULong("〆"),stringToULong("ヵ"),stringToULong("ヶ") 
		, 0 };
	for (unsigned long * Pattern_HArrayP = Pattern_HArray ; *Pattern_HArrayP ; Pattern_HArrayP++ )
	{
		if (c == *Pattern_HArrayP)
		{
			return "H";
		}
	}

	//	this->Pattern[Pattern_I].CreatePattern("[ぁ-ん]");
	if (c >= stringToULong("ぁ") && c <= stringToULong("ん"))
	{
		return "I";
	}

	//	this->Pattern[Pattern_K].CreatePattern("[ァ-ヴーア-ン゙ー]");
	if ((c >= stringToULong("ァ") && c <= stringToULong("ヴ")) || 
		(c >= stringToULong("ア") && c <= stringToULong("ン")) || 
		(c == stringToULong("ー") || c == stringToULong("゙")|| c == stringToULong("ー") ) )
	{
		return "K";
	}

	//	this->Pattern[Pattern_A].CreatePattern("[a-zA-Za-zA-Z]");
	if ((c >= 'a' && c <= 'z') || 
		(c >= 'A' && c <= 'Z') || 
		(c >= stringToULong("a") && c <= stringToULong("z")) || 
		(c >= stringToULong("A") && c <= stringToULong("Z"))  )
	{
		return "A";
	}

//	this->Pattern[Pattern_N].CreatePattern("[0-90-9]");
	if ((c >= '0' && c <= '9') || 
		(c >= stringToULong("0") && c <= stringToULong("9"))  )
	{
		return "N";
	}

	return "O";
}

文字→数字をマクロ展開するともっと早くなると思います。