冬言響 / 日記

アメコミとか映画とか音楽とか猫とか単車とか自転車とか革とか銀とかジーンズとかブーツとか今日喰ったものとか。

RSS2.0

アラビア数字 to 漢数字

逆パターンも作ってみた。

同じ値であっても漢数字で表現しようとすると複数のパターンがあり得る。まあそれで漢数字 to アラビア数字で手間が掛かってた訳なのだけど、とりあえず

Level A
〇?九の文字のみを使う。例:一二三四五六。
Level B
万、億、兆…の文字も使う。例:一二万三四五六。
Level C
十、百、千の字も使う。例:十二万三千四百五十六。

という分け方をして、第二引数でレベルを指定する関数、ということでやってみた。

function asj2ksj($strIn, $lv = "B") { // 第二引数省略時は Level B
if($lv != "A") {
	// -- ここから 2008-12-22 追記。
	// 小数点があった場合。
	if(preg_match("/./", $strIn)) {
		// 小数点以上と以下を切り分ける。
		list($strIn, $strDecimal) = split(".", $strIn);
		// 小数点以下の桁を表す文字テーブル。
		$aryDigitDecimal = array("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x");
		// 間にアルファベットを挟んでいく。
		for($i = 0; $i < strlen($strDecimal); $i++) {
			$strTmp .= $strDecimal{$i} . $aryDigitDecimal[$i];
			}
		// 0 の後にアルファベットがある場合は削除して $strDecimal に入れる。
		$strDecimal = preg_replace("/0[a-z]/", "", $strTmp);
		// $strTmp を初期化。
		$strTmp = "";
		}
	// -- ここまで 2008-12-22 追記。
	// $strIn を 4 桁ごとに区切って配列に格納する。
	while(true) {
		// $strIn が 4 文字以下だったら $aryTmp に追加してループ終了。
		if(strlen($strIn) <= 4) {
			$aryTmp[] = $strIn;
			break;
			}
		// $strIn の末尾 4 字を $aryTmp に追加して、
		// さらにその 4 字ぶん $strIn を削る。
		$aryTmp[] = substr($strIn, strlen($strIn) - 4, 4);
		$strIn = substr($strIn, 0, strlen($strIn) - 4);
		}

漢数字 to アラビア数字のときに「十」「百」「千」…の文字に「A」「B」「C」…とアルファベットを振ったのと逆に、まずそれぞれの文字を表すアルファベットを適当な位置に置いてからまとめて漢数字に置換する。レベル A の場合はアラビア数字をそのまま漢数字に置換するだけなので以下の処理は行われない。

文字列を 4 桁ごとに区切って配列に格納する。for 文で $i を 4 ずつ加算なり減算なりして substr で 4 文字ずつ切り取ってきた方がスマートな気がするのだけどなんかややこしくなってきたのでこうなった。ループ内で元の文字列をガツガツ削る。4 字以下になったらループ終了。

入力値が 1230456789 という文字列であれば「12」「3045」「6789」という 3 つの要素を持つ配列になる。

	if($lv == "C") {
		// 桁を表す文字の配列。千なら「C」、百は「B」、十が「A」で一は空欄。
		$aryDidit = array("C", "B", "A", "");
		for($i = 0; $i < count($aryTmp); $i++) {
			// 配列要素が 4 文字より少ない場合は左側に「0」を加えて字数を揃える。
			$aryTmp[$i] = str_pad($aryTmp[$i], 4, "0", STR_PAD_REFT);
			for($j = 0; $j < 4; $j++) {
				// 配列要素の 1 字目、桁配列の 1 つ目("C")、2 字目、桁の 2 つ目("B")…
				// といった具合に文字を交互に並べて $strTmp に追加。
				$strTmp .= $aryTmp[$i]{$j} . $aryDidit[$j];
				}
			// 「1B(一百)」「1A(一十)」のようになった部分を「B」「A」のみに置換。
			$strTmp = str_replace(array("1A", "1B", "1C"), array("A", "B", "C"), $strTmp);
			// 「0A(〇百)」「1A(○十)」のようになった部分は削除。
			$strTmp = preg_replace("/0[A-C]/", "", $strTmp);
			// 末尾が「0」だったら削除。(2008-12-21 追記)
			$strTmp = preg_replace("/^(.+)0$/", "\1", $strTmp);
			// $strTmp を配列要素に置き換える。
			$aryTmp[$i] = $strTmp;
			// $strTmp を初期化。
			$strTmp = "";
			}
		}

レベル C の場合は「十」「百」「千」を加えるための処理を、配列要素のそれぞれに対して行う。

「12」のように 4 字より少ない場合は 0 を頭に足して字数を揃える。レベル C ではそもそも「〇」の字は使わないので後で除去される。とりあえず字数が揃ってた方が都合が良い。

「A」「B」「C」の字を配列要素の数字の間に挟む。「0012」は「0C0B1A2」に、「3045」は「3C0B4A5」、「6789」は「6C7B8A9」になる。

「十」「百」「千」は「一十」「一百」「一千」のようには表記しないので「1」の次に「A」「B」「C」のいずれかがある場合は「1」を削除する。「0C0B1A2」が「0C0BA2」になる。

「〇十」「〇百」「〇千」も無いので、こちらは「A」「B」「C」の文字ごと削除する。「0C0BA2」が「A2」に、「3C0B4A5」が「3C4A5」になる。

$strTmp を元の配列要素に代入してから初期化して終了。

	// $strIn を一度初期化。
	$strIn = "";
	// 桁を表す文字の配列。
	$aryDidit = array("", "D", "E", "F");
	// $aryTmp の配列要素に↑の文字を追加しながら繋いでいく。
	for($i = 0; $i < count($aryTmp); $i++) {
		// 配列要素左側の「0」を削除(2008-12-21 追記)。
		$aryTmp[$i] = preg_replace("/^0+(.*)$/", "\1", $aryTmp[$i]);
		// ↑結果として配列要素が空文字列になった場合は処理しない(2008-12-21 追記)
		if($aryTmp[$i]) {
			$strIn = $aryTmp[$i] . $aryDidit[$i] . $strIn;
			}
		}
	// 小数点以下の文字列がある場合はそれを接続(2008-12-22 追記)
	if($strDecimal) {
		$strIn .= "." . $strDecimal;
		}
	}

「万」「億」「兆」…用の文字を間に挟みながら配列要素を結合して、$strIn に入れる。レベル B の場合は「12E3045D6789」、レベル C は「A2E3C4A5D6C7B8A9」という文字列になる。

// $strIn の文字を漢字に置換する。
foreach(array(
	"0" => "〇", "1" => "一", "2" => "二", "3" => "三",
	"4" => "四", "5" => "五", "6" => "六", "7" => "七",
	"8" => "八", "9" => "九", "A" => "十", "B" => "百",
	"C" => "千", "D" => "万", "E" => "億", "F" => "兆",
	"G" => "京",
	// 配列のここ以下は 2008-12-22 追記。
	"." => "・", "a" => "分", "b" => "厘", "c" => "毛",
	"d" => "糸", "e" => "忽", "f" => "微", "g" => "繊",
	"h" => "沙", "i" => "塵", "j" => "埃", "k" => "渺",
	"l" => "漠", "m" => "模糊", "n" => "逡巡", "o" => "須臾",
	"p" => "瞬息", "q" => "弾指", "r" => "刹那", "s" => "六徳",
	"t" => "虚空", "u" => "清浄", "v" => "阿頼耶", "w" => "阿摩羅",
	"x" => "涅槃寂静") as $key => $value) {
	$strIn = str_replace($key, $value, $strIn);
	}
return $strIn;
}

レベル A と合流。漢数字 to アラビア数字のときと逆の配列を使って漢数字に置換する。シングルバイト文字からの置換なので str_replace でおk。

レベル A ならば「一二三四〇五六七八九」、レベル B では「一二億三四〇五万六七八九」、C なら「十二億三千四百五万六千七百八十九」という文字列が出力される。終了。

とりあえず昨日の ksj2asj2_test.php のページを asj2ksj 対応版にアップグレード。入力値が不正な場合は「error」が返されます、と。

あー、基本レベル B だけど「10」?「19」だけは「十」?「十九」のように表記する、というパターンを忘れてたな。作ってから気付いた。…まあそのうち。

次はローマ数字…かなあ。機種依存文字を使わずにアルファベットの「I」「V」「X」を使って表現している場合はまずどこからどこまでが 1 字なのかの判別からやらなきゃならさそうだから面倒そうだ。てかそもそもローマ数字の表記の仕方を完全には知らんのだよな。まずそこからか。

不足部分が見つかったのでいくつか追記。「5000000001」を入力したらレベル B で「五〇億〇〇〇〇万〇〇〇一」、レベル C で「五十〇億〇万一」とか出てしまった。

レベル C で配列要素の末尾が「0」だった場合は削除。「〇百」「〇十」を削除するのと目的は同じなのだけど「一」はもともと省略なので漏れてしまってた。

最後に配列要素を結合するとき、まず「0001」のような左側に 0 がある場合はそれを削除。「0000」だったりすれば全削除になる。レベル B のための処理。その結果として配列要素が空文字列になってたら「万」「億」「兆」…を挟む処理も省略。

小数対応した。詳細