【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関数について詳しく知りたいかたは、こちらをご覧ください。

【C++入門】文字列を検索するfind関数(全検索、正規表現)
更新日 : 2019年4月13日

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

【C++入門】vector型の宣言と関数の使い方総まとめ(algorithm)
更新日 : 2019年5月10日

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に変換したいときなどに使ってみてください。

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

LINEで送る
Pocket

SEからWebエンジニアへ転職した理由

侍エンジニア塾卒業生の小池さんは、以前は社内SEとして約5年ほど勤務していました。しかし業務内容は社内のヘルプデスク対応など、プログラムを書く仕事は全くなかったそうです。

SEながらプログラムを書けない現状に「将来仕事がなくなるんじゃないか」と不安を感じ、プログラミング学習を決意。

弊社スクールで学習し、無事ベンチャー企業のプログラマーとして転職に成功しました。そんな小池さんの学習法や転職体験談を伺いましたので、是非ご覧ください。

「プログラミングができないSEは仕事がなくなる」不安を感じたSEが未経験から転職成功するまで
更新日 : 2019年10月7日

書いた人

MoritaIssei

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

おすすめコンテンツ

あなたにぴったりなプログラミング学習プランを無料で診断!

プログラミング学習の効率を劇的に上げる学習メソッドを解説