サイトトップ

Director Flash 書籍 業務内容 プロフィール

Optimizing Performance of ActionScript 3.0

Chapter 07 マニアックなテクニック

□07-02 文字列の検索・置換は正規表現よりStringクラスの方が速い

正規表現(RegExpクラス)を使うと文字列のパターンが定められます。正規表現とStringクラスのメソッドを組合わせると、複雑な文字列の操作ができます。けれど、簡単な検索・置換であれば、Stringクラスのプロパティやメソッドだけを使った方が文字列の扱いは速いです。

お題はつぎの文です。この文の中の「もも」を「みみ」に置換えましょう。ただし、助詞の「」はそのまま残します。

すももも、ももも、もも、もももいろいろ

つまり、置換えた結果はつぎの文になります。

すみみも、みみも、みみ、みみもいろいろ

07-02-01 正規表現で文字列を置換える
まず、正規表現を使った文字列の置換えについておさらいします。正規表現はRegExpインスタンスで定めます。インスタンスはActionScriptのお約束どおり、new演算子でコンストラクタメソッドRegExp()を呼出してつくれます。けれど、パターンをスラッシュ//で括ってリテラル(Word 03-05-001「リテラル」参照)として書くこともできます。リテラルの方が楽でしょう。

var myPattern:RegExp = /パターン/

文字列の検索・置換はString.replace()メソッドで行います。メソッドの第1引数にはRegExpインスタンスで定めた検索する文字列のパターン、第2引数には置換える文字列が渡せます。

文字列.replace(検索パターン, 置換文字列)

正規表現とString.replace()メソッドを使ってお題の検索・置換したのがつぎのスクリプトです。

v ar test_str:String = "すももも、ももも、もも、もももいろいろ";
var result_str:String = test_str.replace(/もも/g, "みみ");
trace(result_str);   // 出力: すみみも、みみも、みみ、みみもいろいろ

RegExpインスタンスの使い方についてふたつ補足します。第1は、スラッシュ//で括った中に書く文字列(もも)は、ダブルクォーテーション("")で囲みません。第2は、スラッシュ//の後に添えたフラグgの意味です。このフラグがないと、検索対象として参照するお題の文の中で、パターンに当てはまる最初の文字列だけの指定になります。文中のパターンに当てはまるすべての文字列を対象とするには、globalフラグgを加えなければならないのです。

Basics 07-02-001■正規表現による文字列の操作
正規表現による文字列の操作については、gihyo.jp連載「ActionScript 3.0で始めるオブジェクト指向スクリプティング」第28回「正規表現で文字列を扱う」<http://gihyo.jp/dev/serial/01/as3/0028>をお読みください。


07-02-02 String.split()とArray.join()メソッドで文字列を置換える
String.split()Array.join()というふたつのクラスのメソッドを組合わせても、簡単な検索・置換ができます。まず、String.split()メソッドは、参照する文字列を引数の文字列で切離し、切り分けられた文字列がエレメントとして納められた配列を返します。このとき、引数に定めた区切り文字は文字列から除かれます。

var result_array:Array = 文字列.split(区切り文字)

つぎに、Array.join()メソッドは、String.split()メソッドとは逆に、配列エレメントをつなげて文字列にして返します。そのとき、引数として渡した文字列をエレメントの間に挿入します。

var result_str:String = 配列.join(挿入文字)

これらふたつのメソッドを使ってお題の文中の文字列を置換えたのがつぎのスクリプトです。String.split()メソッドで検索文字列が一旦文から除かれ、そこにString.split()メソッドが置換文字列を挿入しています。

var test_str:String = "すももも、ももも、もも、もももいろいろ";
var result_array:Array = test_str.split("もも");
trace(result_array);   // 出力: す,も、,も、,、,もいろいろ
var result_str:String = result_array.join("みみ");
trace(result_str);   // 出力: すみみも、みみも、みみ、みみもいろいろ

このString.split()Array.join()メソッドを使った置換えの方が、正規表現を使うより速いようです。


07-02-03 Stringクラスのプロパティとメソッドで文字列を置換える
さらに、Stringクラスのプロパティとメソッドを使って検索文字列を文の頭からひとつひとつ探し、置換文字列に差替えて、文をつぎはぎした方が扱いはより速くなります。

まず、参照する文字列の中の検索文字の位置を調べるのが、String.indexOf()メソッドです。返される文字の位置は0から始まるインデックスで、参照する文字列の中で検索文字が最初に見つかったインデックスを示します。オプションの第2引数には、検索を始めるインデックスが渡せます。よって、検索文字が最初に見つかったインデックスに検索文字数を加えて第2引数に渡せば、つぎの検索文字のインデックスが得られます。

var index:int = 文字列.indexOf(検索文字, 検索開始位置)

forループで検索文字を順に探していけば、すべての検索文字のインデックスが調べられます。検索文字が見つからないときは-1が返されますので、この戻り値をもってforループの終了とします(継続条件としては、「-1 < 戻り値」となります)。

つぎに、文字列から一部を切り出すのが、String.substring()メソッドです。ふたつの引数には、それぞれ開始位置と終了位置のインデックスを定めます。戻り値は取出された文字列です。

var result_str:String = 文字列.substring(開始位置, 終了位置)

Tips 07-02-001■文字列を切り出すインデックスは仕切りに振った番号と捉える
[ヘルプ]でString.substring()メソッドの第2引数について確かめると、渡すのは「最後の文字のインデックスに1を加えた整数」だと説明されています。しかし、なぜ第2引数の数値にだけ「1を加え」るのか理由がわかりません。これは、インデックスを文字ではなく、文字の間の仕切りにつけられた通し番号だと捉えればよいのです(図07-02-001)。

図07-02-001■文字の間の仕切りにインデックスを振る

仕切りのインデックスで文字の範囲を定める。

文字の間の仕切りで考えるなら、お題の頭の「すももも、」の中の「もも」という文字は、仕切り1から3の範囲にあります。したがって、つぎのようにこのふたつのインデックスをString.substring()メソッドに渡せば、文字列が正しく取出せます。

var test_str:String = "すももも、";
var result_str:String = test_str.substring(1, 3);
trace(result_str);   // 出力: もも

あとは、文字列の長さ(文字数)をString.lengthプロパティで調べ、連結演算子+で文字列をつなげることがおわかりになれば、スクリプトを書く準備は整いました。文字列の検索・置換を、つぎのスクリプト07-02-001のように関数(xReplace())として定義します。

スクリプト07-02-001【○】Stringクラスのプロパティとメソッドで文字列の置換えをする関数の定義
  1. var test_str:String = "すももも、ももも、もも、もももいろいろ";
  2. var result_str:String = xReplace(test_str, "もも", "みみ");
  3. trace(result_str);   // 出力: すみみも、みみも、みみ、みみもいろいろ
  4. function xReplace(source_str:String, find_str:String, replace_str:String):String {
  5.   var numChar:uint = find_str.length;
  6.   var result_str:String = "";
  7.   var end:int;
  8.   for (var i:uint = 0; -1 < (end = source_str.indexOf(find_str, i)); i = end + numChar) {
  9.     result_str += source_str.substring(i, end) + replace_str;
  10.   }
  11.   result_str += source_str.substring(i);
  12.   return result_str;
  13. }

関数(xReplace())の中で肝になるのは、forループで文字列をつぎはぎする操作です(スクリプト07-02-001第8〜10行目)。検索開始位置(i)から検索文字(find_str)の見つかった位置(end)まで取出した文字列に、置換文字(replace_str)を足して置換後の文字列(result_str)としてつなげるという操作(第9行目)が繰返されています。forループの継続条件でもう検索文字が見つからなくなり、String.indexOf()メソッドが-1を返すまで処理は続けられます。

Tips 07-02-002■forループの継続条件に代入式を組込む
スクリプト07-02-001のforループ継続条件は、左辺が以下の代入式になっています(第8行目)。つまり、String.indexOf()メソッドで検索文字の位置を変数(end)に代入したうえで、その値が-1ではない(-1 < end)つまり検索文字はまだあることを繰返しの条件にしています。

end = source_str.indexOf(find_str, i)

そして、forループの更新処理では、検索開始位置をすでに見つかった検索文字の後に移します(i = end + numChar)。これで、検索文字すべてを置換える操作が行えるのです。継続条件に代入式を使わない場合は、whileループでつぎのように書くこともできます。

  1.   // var end:int;
      var i:uint = 0;;   // 追加
      var end:int = source_str.indexOf(find_str, i);   // 修正
  2.   // for (var i:uint = 0; -1 < (end = source_str.indexOf(find_str, i)); i = end + numChar) {
      while (-1 < end) {   // 修正
  3.     result_str += source_str.substring(i, end) + replace_str;
        i = end + numChar;   // 追加
        end = source_str.indexOf(find_str, i);   // 追加
  4.   }

前述07-02-01および07-02-02と比べると、スクリプトは明らかに長くなりました。けれど、正規表現のように複雑なパターンを扱うことはなく、StringとArrayといったふたつのクラスのインスタンスをつくったりもしません。ただ、文字列をひと文字ずつ調べて、取出し、つなげるだけの単純な操作の繰返しです。それが処理の速さにつながっているのでしょう。

[*筆者用参考]「文字列の検索・置換は正規表現よりStringクラスのメソッドを使う方が速い


作成者: 野中文雄
作成日: 2011年9月7日


Copyright © 2001-2011 Fumio Nonaka.  All rights reserved.