Idiom: Check if given string starts with some string

std::stringとして与えられた文字列がある特定の文字列から始まるかどうかを記述するのは簡単なことのように思えます。これが自分にとっては意外に難しく、いつも時間を割いてしまうのです。先日、コードリーディングをしていた際にうまい方法を見つけたため、イディオムとして記載しておこうと思います。

先日(またもや)やってしまった実装は以下のようなものでした。

  const std::string s("abc");   // abcから始まる文字を探したい

  const int size = s.size();

  const std::string a[] = {
    "abcdef",
    "defabc",
    "abcabc",
    "a"
  };

  for (const auto& i : a)
    if (i.size() >= size)               // 文字列の長さが十分かどうかチェックする
      if (i.substr(0, size) == s)       // 先頭の文字列を切り出して比較する
        // 文字列iはsから始まっている!
};

この実装は色々と無駄があるだけではなく、自分にとっては注意すべき点が多く手早く書けないのです。

  • 文字列iのサイズを検証する -> この際等号を入れるべきだろうか?
  • 文字列iの先頭size文字をstd::stringとして生成する -> この際使うべきsubstrメソッドには引数を1つ与えるべきか。それとも2つ与えるべきか?

そもそもsubstrメソッドは末尾のサイズを適切にチェックしてくれるはずです。

  for (const auto& i : a)
    // if (i.size() >= size)            // サイズのチェックはしなくてよい
      if (i.substr(0, size) == s)
        // 文字列iはsから始まっている!

これでも悪くはないのですが、やはりsubstrの引数の数は悩んでしまいそうです。どうもあれだなあ、とコードリーディングをしていた際に、以下のイディオムを見つけました。

  for (const auto& i : a)
    if (i.find(s) == 0)
      // 文字列iはsから始まっている!

なるほど簡潔で覚えやすそうです。次回からはこのイディオムを使おうと思います。

もう少しだけ

findメソッドを使ったこのイディオムは簡潔でよいのですが、先頭から指定して文字列で始まっていない場合、無駄に探索を行ってしまいます(例えば文字列が"xyzxyzxyzxyzxyzxyzxyz"の場合)。これが無駄に思える場合はstrncmpを使った方がいいかもしれません。

  for (const auto& i : a)
    if (strncmp(i.c_str(), s.c_str(), size) == 0)
      // 文字列iはsから始まっている!