スポンサーリンク

JavaScriptの動かないコード (中級編) 正規表現で同じ文字の連続を検出したい - 置換前パターン中での後方参照


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • 同じアルファベットの連続した並びを,それぞれ1文字にまとめる。

例:

  • "good apple" → "god aple"
<script language="JavaScript">

function matomeru( str )
{
	// 正規表現を使って置換する。
	str = str.replace(
		// 同一文字の 2 回以上の繰り返しを検出して,
		// ( ) 内にそのアルファベットを保管する。
		/([a-zA-Z]){2,}/g,

		// ( ) 内に入っている1文字のアルファベットにする
		 "$1"
	);
	return str;
}

// "god aple" と表示。
alert( matomeru( "good apple" ) );

</script>

発生する不具合


"god aple" ではなく, "d e" と表示されてしまう。

やりたいことが実現できず困る

正規表現を学びたての場合,以下の事は簡単だ。

  • (1) 任意の文字の連続した並びを検出する。 → /.+/ で "abc123" にマッチ
  • (2) ある特定の一つの文字の連続した並びを検出する。 → /a+/ で "aaa" にマッチ
  • (3) ある特定の文字グループの並びを検出する。 → /[a-z]+/ で "abc" にマッチ

では,

  • ある特定の文字グループの同じ文字が連続した並びを検出する。

のは,どうやるのか?


// a の2回以上の並びか,もしくは
// b の2回以上の並びか,もしくは
// c の2回以上の並びか,もしくは
// d の2回以上の並びか,もしくは
//  ・・・・・
// にマッチ。

/(a{2,}|b{2,}|c{2,}|d{2,}|e{2,} ・・・・・ )/g

これは冗長すぎる。


かわりに,冒頭のコードのように, [a-z] でアルファベット全体を表現すると

// 【アルファベットの】2回以上の並びにマッチ。
/([a-z]){2,}/g

となり,"apple" は「アルファベットが5つ並んでいる!」とみなされ,なんと単語全体で1文字に凝縮されてしまう。


やりたい事は,あくまで同じ文字の重複した並び(ここでは,"pp" だけ)なのだが。

そこで後方参照

ここで,後方参照の出番である。


後方参照とは,

  • 置換前の文字列中では,「\1」「\2」など,バックスラッシュに続けて数字を書く
  • 置換後の文字列中では,「$1」「$2」など,ドル記号に続けて数字を書く

事によって利用するもので,
これを使うと,かっこでくくられた評価済みの文字列を表す。


「評価済みの」というのがポイントである。

	([a-z]){2}

のようなグループ記法は,あくまで

	[a-z][a-z]

というような事をやっているに過ぎない。

[a-z] という文字クラス中のどの文字にマッチするのか?という評価は,[a-z] が現れるたびに,その都度行なわれる。

なので,直前で評価された結果の文字列を保管できない。


ところが,これを後方参照を使って修正すると

	([a-z])\1

となる。 \1 は,1つ目の括弧の中身の評価済み文字列であるから,上記の表現は aa, bb, cc, ... などにマッチする。

2文字を超える並びを検出したいのであれば,+ を使って,\1 が1回以上繰り返されると指定すればよい。

修正案

正しいコードは下記のようになる。

<script language="JavaScript">

function matomeru( str )
{
	str = str.replace(
		// 同一文字の 2 回以上の繰り返しを検出して,
		// ( ) 内にそのアルファベットを保管する。
		/([a-zA-Z])\1+/g,

		// ( ) 内に入っている1文字のアルファベットにする
		 "$1"
	);
	return str;
}

// "god aple" と表示。
alert( matomeru( "good apple" ) );

</script>

知らないと実装できない

さて,上記の事を達成したいとして,やり方を探したい人は,Googleでどのように検索したらよいのか?


「正規表現 同じ文字 連続 並び クラス内」など必死に検索しても,出てくるサイトの記述は,上で挙げた

  • 1,任意の文字の連続した並びを検出する方法(/.+/)
  • 2,ある特定の一つの文字の連続した並びを検出する方法(/a+/)
  • 3,ある特定の文字グループの並びを検出する方法(/[a-z]+/)

のような物だけかもしれない。


このような困難を回避するためには,後方参照という方法の存在を事前に知っているしかない。

しかし,後方参照について述べているサイトのほとんどは $1 による「置換後のパターン中での後方参照」について述べているのみであり,\1 を使った「置換前のパターン中での後方参照」について述べた資料は非常に少ない。


数少ない言及例として,下記サイトなどを参照。

JavaScript の正規表現の書式
http://homepage2.nifty.com/magicant/s...

\1、\2、\3…

\1 や \2 はそれぞれ 1 番目、2 番目の捕捉括弧の内容を表す。例えば、 (a+)(b+)c\2\1 というパターンは abbcbba や aaabcbaaa にマッチする。

情報の需要はあるはずなのだが。


やり方がわからない場合は,文字列を for 文で頭から 1 文字ずつ検査し,前の文字と次の文字が同じかどうか判定していく,などの代替手段があるのだろう。