4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ヒットした位置に1を足した位置から検索したからといって、直後の検索対象がヒットすると思ったか?

Posted at

JavaScript における開始位置を指定した文字列検索

JavaScript では、文字列内の位置は 0-origin で指定する。
indexOf() 関数では、第2引数で検索を開始する位置を指定できる。

JavaScript で開始位置を指定した文字列検索をするサンプル
const data = "宇宙パトロールルル子";
let pos = 0;
while (pos >= 0) {
  pos = data.indexOf("", pos);
  if (pos >= 0) {
    console.log(`${pos + 1}文字目`);
    pos++;
  }
}

このプログラムを実行すると、以下の出力が得られる。

7文字目
8文字目
9文字目

ヒットした位置に1を足した位置から検索することで、直後の検索対象を見つけることができている。

なでしこ における開始位置を指定した文字列検索

なでしこ文字列を扱う命令の多くは、位置を 1-origin で指定するようである。
文字検索」命令を用いることで、文字列内の指定した位置から対象の文字列を検索することができる。

なでしこ で開始位置を指定した文字列検索をするサンプル
データは「宇宙パトロールルル子」。
位置は1。
位置が0超の間繰り返す
  位置はデータで位置から「ル」を文字検索。
  もし、位置が0超ならば
    「{位置}文字目」と表示。
    位置を1だけ増やす。
  ここまで。
ここまで。

このプログラムを v3.6.45 の簡易エディタで実行すると、以下の出力が得られる。

7文字目
9文字目

0-origin と 1-origin の読み替えを行っただけで、JavaScript 版と同様の処理をしているはずなのに、なぜか「8文字目」が抜けてしまっている。

実は、文字列を扱う多くの命令は位置を 1-origin で指定しているが、この「文字検索」命令の検索開始位置は、なぜか 0-origin で指定するのである。

マニュアルをよく見ると、

「しんぶんし」で4から「ん」を文字検索。

の結果が 0 (見つからない) であることや、「再帰で全て検索する例」の例で検索を 0 から始めていることで、検索開始位置が 0-origin であることを匂わせている。

匂わせじゃなくてはっきり 0-origin だって書けばいいのに…… (例のロボット)

プロデル における開始位置を指定した文字列検索

文字数の数え方については なでしこ と違って一貫性がありそうだった プロデル でも、同様に検索をしてみよう。
プロデルでも、文字列中の位置の指定は 1-origin が基本のようである。
探す」手順を用いることで、文字列内の指定した位置から対象の文字列を検索することができる。

プロデル で開始位置を指定した文字列検索をするサンプル
データは「宇宙パトロールルル子」
位置は1
位置が0より大きい間、繰り返す
  データの(位置)文字目から「ル」を探して、位置とする
  位置が0より大きいなら
    「[位置]文字目」を出力して改行する
    位置を位置+1とする
  そして
そして

このプログラムをバージョン 1.9.1273 のスミレ畑で実行すると、以下の出力が得られる。

7文字目
9文字目

なんと、プロデルでも 「8文字目」が抜けてしまった。
プロデルでも検索開始位置の指定はまさかの 0-origin、一貫性なしということである。

同じ「文字目」という表現を使う手順でも、例えば「取り出す」では 1-origin で位置を指定している。

さらに、プロデルの「探す」のサンプルを見てみると

内容は「にほんごぷろぐらみんぐげんご ぷろでる」
内容の5文字目から「ん」を探して、文字位置とする

となっている。
この文字列の「5文字目」からの検索では、0-origin でも 1-origin でも結果は変わらない。
なでしこ と違って、0-origin であることを匂わせてもいないのである。
もちろん、0-origin であることを明示もしていない。

文字数の数え方については なでしこ よりも一貫性があって良さそうだった プロデル だが、文字列検索の開始位置の指定については なでしこ と同様の罠 (他の多くの文字列操作命令が 1-origin を用いる中、0-origin を採用している) があり、その上 なでしこ よりもさらにこのような罠がある (0-origin である) ことをわかりにくくしており、より悪質といえるだろう。

プロデルで始める日本語プログラミング言語入門(#10) 「文字列あれこれ」 - プロデルブログ
においても、

文字列から検索する

文字列は先頭を1文字目として数えます。

とはっきり書かれており、検索開始位置の指定の特殊さへの言及は見当たらない。

ちなみに、「前方向へ探す」手順についても、検索開始位置の指定は 0-origin、検索結果は 1-origin のようである。

プロデルの「前方向へ探す」手順を使うサンプル
内容は「宇宙パトロールルル子」
開始位置は8
内容の(開始位置)文字目から「ル」を前方向へ探して、発見位置とする
「[開始位置]文字目から前方向に探して、[発見位置]文字目に見つかりました」を出力する

このプログラムをスミレ畑で実行すると、以下の出力が得られた。

8文字目から前方向に探して、9文字目に見つかりました

8文字目から前方向に探しているのに、それより数値が大きい9文字目に見つかっているというのは、とっても変な感じ……

まとめ

JavaScript の indexOf による文字列検索では、検索開始位置の指定は他の文字列操作関数と同じ 0-origin であり、素直な仕様である。
一方、なでしこの「文字検索」や、プロデルの「探す」「前方向に探す」では、他の文字列操作命令における位置の指定やこの命令の検索結果は 1-origin であるにもかかわらず、検索開始位置の指定はこれらとは異なって 0-origin である。

0-origin を採用するか 1-origin を採用するかは、言語やライブラリの仕様として決めることであり、どっちが正解というものではない。
しかし、同じ言語やライブラリの中では、統一しておいたほうがわかりやすく、使いやすいだろう。
特に、プロデルではドキュメントから検索開始位置の指定方法が他の文字列操作命令における位置の表現とは異なることを読み取るのは難しそうであり、かなり不親切だといえそうである。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?