※追記(2014/02/212015/03/24)
最新のHSPには文字列置換のための命令 strrep が追加されています。
実際に使う場合はこれを利用してください。
開発とかの関係で置換の実験をしてみたので、記憶が新しいうちに書くことにしました。
今書かないと、記憶が飛びそうなんで……
疑問1:置換とは?
おそらく、この疑問を持つ人は少ないでしょう。
スクリプトエディタに優秀な置換機能がついているので、その恩恵を受けている人が多いかと思います。
一応説明しておきますと、文字列の中から特定の単語を抜き出して、それを別の文字列に変更することを、置換といいます。
これは非常に便利なので、利用できるようにしましょう。
疑問2:どうやるの?
方法は、いくらでもあります。
今回はいくつもの方法で、やっていくことにしましょう。
おそらく、もっともシンプルなやり方です。
とりあえず、読み替え法とでも言いましょう。(適当に命名)
こんなものは、ちゃちゃっと作ってしまいます。
#module #defcfunc replace str p1, str target, str after dim len, 2 len = strlen(p1), strlen(target) sdim string, len + 1 // 置換前文字列を格納する変数を用意 string = p1 // instr するので、いったん変数に移す i = 0 // リセット repeat n = instr(string, i, target) if ( n < 0 ) { break } i += n string = strmid(string, 0, i) + after + strmid(string, i + len(1), len(0) - i) i += len(1) loop return string #global
使用例:
buf = "ABCDEFG ABCDEFG" mes replace(buf, "ABC", "あいう")
ずいぶん適当ですが、しっかり置換できているようですね。
この関数がしている置換方法は、以下の通りです。
しかし、この関数には問題があります。
とにかく、元の文字列が長くなると、極端に遅くなるのです。
原因は、string = strmid + after + strmid の式で、見て分かるとおり、同じ文字列をなんども string に格納しています。
一回だけで十分のはずなのに……。何回もするのは無駄でしかありません。
さて、次は、すべての文字を一回づつコピーしていきます。
やり方を説明していきましょう。
基本的に読み替え法と同じですが、今回は作業バッファが必要です。
※作業バッファとは、読んで字の如く「作業の為に使う領域」です。
文字列の先頭から、instr() 関数で検索し、見つけた場所までをいったん、作業バッファにコピーします。
次に、作業バッファに置換語を付け加えます。
その後、元の文字列の「検索語を飛ばしたところ」から、次を検索します。
それで、見つかった場所までをコピー。あとは繰り返しです。
今回もちゃちゃっと組んじゃいましょう。
#module #defcfunc replace str p1, str target, str after dim len, 2 len = strlen(p1), strlen(target) sdim string, len + 1 // 置換前文字列を格納する変数を用意 string = p1 // instr するので、いったん変数に移す sdim buf, len + 1 // 作業バッファ i = 0 // リセット wrote = 0 repeat n = instr(string, i, target) memexpand buf, wrote + n + 16 // 拡張する。 if ( n < 0 ) { // 即終了ではなく、元の文字列の残りをコピーする // そうしないと、後ろが切れることになります poke buf, wrote, strmid(string, i, len(0) - i) break } poke buf, wrote, strmid(string, i, n) + after i += n + len(1) loop return buf // 作業バッファを返す #global
特に説明すべきことはありませんが、一つだけ。
この関数は、文字列を後から付けくわえる時、+= 演算子ではなく、poke 命令を使っています。
この命令は、+= のように自動的にバッファを確保してくれない代わりに、より早く動作します。
ちなみに、バッファを確保するのには memexpand 命令を用います。
本当は、確保し直さなくても大丈夫なときにするんですけどね。この方法。
さっきの方法でも、普通なら十分な程度の性能でした。
しかし、あれでもまだ「無駄」はあるのでしょう?。
そう、関係ない部分をコピーする必要は無いはずです!!
今回はわかりやすい題にもあるとおり、検索語を「消して」、置換語を「書き加え」るという方法で置換しようと思います。
とりあえず、今回の方法には文字列を挿入する命令と、文字列を削除する命令が必要です。
HSP3.1 にはないので、マクロを使いましょう。
// 文字列操作マクロ #define global StrDel(%1, %2 = 0, %3 = 0) \ memcpy (%1), (%1), strlen((%1)) - ((%2) + (%3)), (%2), (%2) + (%3) :\ memset (%1), 0, (%3), (%2) + (%3) #define global StrInsert(%1, %2 = "", %3 = 0) \ _StrInsert_len@ = strlen(%2) :\ sdim _StrInsert_temp@, (_StrInsert_len@ + 2) : _StrInsert_temp@ = (%2) :\ memexpand (%1), strlen((%1)) + _StrInsert_len@ + 1 :\ memcpy (%1), (%1), strlen(%1) - (%3), (%3) + _StrInsert_len@, (%3) :\ memcpy (%1), _StrInsert_temp@, _StrInsert_len@, %3, 0
StrDel マクロ:消す部分を、その後に続く文字列を前にずらして上書きしている。
StrInsert マクロ:挿入する部分を広げて、空いたところにに文字列をコピーしている。
この二つのマクロを使って置換します。
#命令にした方がいいかもしれない。
#module // 文字列操作マクロ #define StrDel(%1,%2=0,%3=0) memcpy %1,%1,strlen(%1)-((%2)+(%3)),(%2),(%2)+(%3):memset %1,0,%3,(%2)+(%3) #define StrInsert(%1,%2="",%3=0) _StrInsert_len@ = strlen(%2):\ sdim _StrInsert_temp@,_StrInsert_len@+2:_StrInsert_temp@=%2:\ memexpand %1,strlen(%1)+_StrInsert_len@+1:\ memcpy %1,%1,strlen(%1)-(%3),(%3)+_StrInsert_len@,%3:\ memcpy %1,_StrInsert_temp@,_StrInsert_len@,%3,0 #defcfunc replace str p1, str target, str after dim len, 2 len = strlen(target), strlen(after) sdim string, len + 1 string = p1 i = 0 // リセット repeat n = instr(string, i, target) if ( n < 0 ) { break } StrDel string, i + n, len(0) // 削除 StrInsert string, after, i + n // 挿入 i += n + len(1) loop return string #global
とりあえず作りました。とってもシンプルですね。
さて、次に作る関数には、重大な欠点があります。
検索語と置換語の長さが同じでないと、使えません。
どんなものか、作ってみましょう。
#module #defcfunc replace str p1, str target, str p3 len = strlen(p3) if ( strlen(target) != len ) { return p1 } sdim string, strlen(p1) + 1 string = p1 sdim after, len + 1 after = p3 i = 0 // リセット repeat n = instr(string, i, target) if ( n < 0 ) { break } memcpy string, after, len, i + n, 0 i += n + len loop return string #global
とりあえず実行してみたところ、今までのものよりずいぶん速く置換できます。
memcpy は速い、と覚えておいてください。
( memcpy 命令は、メモリに直接書き込んでいるので、高速です。)
しかし、検索語と置換語が同じ長さなんて、たまにしか起こらないので、この関数は使い道が無いわけです。
関数というのは、もっと汎用的であるべきでしょう。
というわけで、次で克服してみます。
今回も、この前のマクロを使います。
やり方はこうです。
検索語と置換語の長さを調べ、
検索語の方が長ければ、StrDel で、不要な部分を削り、
置換語の方が長ければ、StrInsert で、書き込める領域をのばします。
そうすれば、memcpy を使って上書きすることが可能です。
#module #defcfunc replace str p1, str target, str p3 dim len, 2 dim num, 2 len = strlen(target), strlen(p3) if ( len(0) > len(1) ) { num = len(0) - len(1), 1 } else { num = len(1) - len(0), 2 sdim tmp, num + 1 // 挿入する文字列を作成 repeat num poke tmp, cnt, 0x20 // 半角スペース loop } sdim string, strlen(p1) + 1 string = p1 sdim after, len(1) + 1 after = p3 i = 0 // リセット repeat n = instr(string, i, target) if ( n < 0 ) { break } if ( num(1) == 1 ) { // 1 なら Del StrDel string, i + n, num } else : if ( num(1) == 2 ) { // 2 なら Insert StrInsert string, tmp, i + n } memcpy string, after, len(1), i + n, 0 i += n + len(1) loop return string #global
後半がひどくテキトーな気がしますが、ここでお開きです。
では、また次回。