【C++入門】文字列を分割するsplit関数の実装

C++ではsplit関数が存在しないので、stringを使って文字列を分割してvectorに変換する場合には自分で実装する必要があります

この記事では、

  • split関数とは
  • find_first_of関数を使った実装
  • 正規表現を使った実装
  • 時間計測をしてどれが一番効率的か

などの基本的な内容から、応用的な使い方についても解説します。

目次

split関数とは

Javascriptなどの他の多くの言語ではsplit関数をサポートしています。この関数は、文字列から”,”(カンマ)などの区切り文字で区切って配列にするために使います

しかし、C++ではsplit関数は実装されていません。そのため、自分で実装する必要があります。

この記事ではさらに、どの方法で実装したsplit関数が一番早いのかも解説します。

find_first_ofで実装する方法

C++では、文字列から特定の文字を検索するために、find_first_of関数を使います。これを使い、区切り文字が見つかったところまで文字列を取得してvectorに追加します。

vectorというのは、長さを変えることのできる配列のことです。指定した区切り文字で文字列を区切るsplit関数を実装する方法を次のプログラムで確認してみましょう。

#include <iostream>
#include <string>
#include <vector>

std::vector<std::string> split(std::string str, char del) {
    int first = 0;
    int last = str.find_first_of(del);

    std::vector<std::string> result;

    while (first < str.size()) {
        std::string subStr(str, first, last - first);

        result.push_back(subStr);

        first = last + 1;
        last = str.find_first_of(del, first);

        if (last == std::string::npos) {
            last = str.size();
        }
    }

    return result;
}

int main() {
    std::string str = "samurai,engineer,programmer,se";
    char del = ',';

    for (const auto subStr : split(str, del)) {
        std::cout << subStr << 'n';
    }

    return 0;
}

実行結果:

samurai
engineer
programmer
se

find_first_of関数について詳しく知りたいかたは、こちらをご覧ください。

vectorについて詳しく知りたい方はこちらをご覧ください。

for文で実装する方法

次に、find関数を使わずに文字列を1文字ずつ確認する方法で実装します。

まず、文字列を一時的に保管するsubStrという変数を作ります。次に、範囲for文を使って文字列から文字を一つづつ取り出し、文字がカンマなどの区切り文字かどうかを調べます。

そしてその文字が区切り文字が見つかるまでsubStrに文字を追加していき、区切り文字の場合はそこまでのsubStrを配列に追加し、subStrの中身を削除します。

それを文字列の最後まで続けていくと、配列の後ろに1つづつ文字列が追加されていきます。

#include <iostream>
#include <string>
#include <vector>

std::vector<std::string> split(std::string str, char del) {
    std::vector<std::string> result;
    std::string subStr;

    for (const char c : str) {
        if (c == del) {
            result.push_back(subStr);
            subStr.clear();
        }else {
            subStr += c;
        }
    }

    result.push_back(subStr);
    return result;
}

int main() {
    std::string str = "samurai,engineer,programmer,se";
    char del = ',';

    for (const auto subStr : split(str, del)) {
        std::cout << subStr << 'n';
    }

    return 0;
}

実行結果:

samurai
engineer
programmer
se

正規表現を使って実装する方法

正規表現を使うことでも実装することができます。

正規表現を使って検索するには、regex_search関数を使います。regex_search関数を使うには、regexというライブラリーをインクルードする必要があります。

これを使うことで、文字列から簡単に数値のみを取り出したり、特定の文字で区切ることができます。

#include <iostream>
#include <string>
#include <vector>
#include <regex>

std::vector<std::string> split(std::string str, char del) {
    std::vector<std::string> result;
    std::string searchStr = std::string("(w+)(") + del + ')';

    std::regex re(searchStr);
    std::smatch m;

    while (std::regex_search(str, m, re)) {
        result.push_back(m[1].str());

        str = m.suffix();
    }

    result.push_back(str);

    return result;
}

int main() {
    std::string str = "samurai,engineer,programmer,se";
    char del = ',';

    for (const auto subStr : split(str, del)) {
        std::cout << subStr << 'n';
    }

    return 0;
}

実行結果:

samurai
engineer
programmer
se

どれが一番速いか?

さて、これまで様々な方法でsplit関数を実装してきましたが、どれが実用的なのでしょうか?

そこで、処理にかかる時間を計測してみます。計測のため、ひとつは、1文字ずつ”,”(カンマ)で区切れている文字列を色々なsplit関数を使って計測する。

もう一つは、1区切りずつの文字数が大きい文字列を使って計測してみます。今回、計測のためにはtime.hというライブラリのclock関数を使います。

#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <time.h>

std::vector<std::string> split_find(std::string str, char del) {
    int first = 0;
    int last = str.find_first_of(del);

    std::vector<std::string> result;

    while (first < str.size()) {
        std::string subStr(str, first, last - first);

        result.push_back(subStr);

        first = last + 1;
        last = str.find_first_of(del, first);

        if (last == std::string::npos) {
            last = str.size();
        }
    }

    return result;
}

std::vector<std::string> split_for(std::string str, char del) {
    std::vector<std::string> result;
    std::string subStr;

    for (const char c:str) {
        if (c == del) {
            result.push_back(subStr);
            subStr.clear();
        }else {
            subStr += c;
        }
    }

    result.push_back(subStr);
    return result;
}

std::vector<std::string> split_regex(std::string str, char del) {
    std::vector<std::string> result;
    std::string searchStr = std::string("(w+)(") + del + ')';

    std::regex re(searchStr);
    std::smatch m;

    while (std::regex_search(str, m, re)) {
        result.push_back(m[1].str());

        str = m.suffix();
    }

    result.push_back(str);

    return result;
}

int main() {
    std::string shortSplitedStr;
    std::string longSplitedStr;
    char del = ',';
    char c = 'a';

    const int N = 100000;
    const int M = 100;

    for (int i = 0; i < N; i++) {
        shortSplitedStr.push_back(del + c);
    }

    for (int i = 0; i < M; i++) {
        longSplitedStr += std::string(N, c) + del;
    }

    std::cout << "細かく切れた文字列を区切るn";

    {
        clock_t start = clock();
        split_find(shortSplitedStr, del);
        clock_t end = clock();
        std::cout << "find関数を使うと、" << (double)(end - start) / CLOCKS_PER_SEC << "秒.n";
    }
    {
        clock_t start = clock();
        split_for(shortSplitedStr, del);
        clock_t end = clock();
        std::cout << "1文字ずつみていくと、" << (double)(end - start) / CLOCKS_PER_SEC << "秒.n";
    }
    {
        clock_t start = clock();
        split_regex(shortSplitedStr, del);
        clock_t end = clock();
        std::cout << "正規表現を使うと、" << (double)(end - start) / CLOCKS_PER_SEC << "秒.n";
    }

    std::cout << "n1区切りが長い文字列を区切るn";

    {
        clock_t start = clock();
        split_find(longSplitedStr, del);
        clock_t end = clock();
        std::cout << "find関数を使うと、" << (double)(end - start) / CLOCKS_PER_SEC << "秒.n";
    }
    {
        clock_t start = clock();
        split_for(longSplitedStr, del);
        clock_t end = clock();
        std::cout << "1文字ずつみていくと、" << (double)(end - start) / CLOCKS_PER_SEC << "秒.n";
    }
    {
        clock_t start = clock();
        split_regex(longSplitedStr, del);
        clock_t end = clock();
        std::cout << "正規表現を使うと、" << (double)(end - start) / CLOCKS_PER_SEC << "秒.n";
    }

    return 0;
}

実行結果:

細かく切れた文字列を区切る
find関数を使うと、0.000287秒.
1文字ずつみていくと、0.001314秒.
正規表現を使うと、0.197776秒.

1区切りが長い文字列を区切る
find関数を使うと、0.01828秒.
1文字ずつみていくと、0.125721秒.
正規表現を使うと、14.2955秒.

この結果をみてみると、find関数を使った方法が一番速いとわかります。

また、正規表現を使ったやり方が一番遅いこともわかります。ただ、実用上はどれを使ってもあまり変わらない程度の差ですので、あまり気にしなくてもいいでしょう。

まとめ

いかがだったでしょうか?

今回はsplit関数を実装する方法を解説しました。文字列を区切り、vectorに変換したいときなどに使ってみてください。

もし、文字列から配列を作る方法を忘れてしまったらこの記事を確認してください。

この記事を書いた人

ご閲覧いただきありがとうございます。森田一世と申します。プログラマーとしてRaspberry piを使ったり、記事を作成しています。

目次