質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

CRLF

CRLFは、改行コードのことです。 改行コードは、改行を表す制御文字です

Q&A

解決済

6回答

46309閲覧

[C++][std::string]std::string型の文字列の改行コードを高速で取り除く方法について

evans

総合スコア48

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

CRLF

CRLFは、改行コードのことです。 改行コードは、改行を表す制御文字です

0グッド

1クリップ

投稿2016/05/08 13:55

編集2016/05/08 13:56

###前提・実現したいこと
c++のstd::string型の文字列に含まれる改行コードのみを高速で取り除きたい
c++11, c++14の機能は使用せず実現したい

###発生している問題・エラーメッセージ
改行を含む文字ファイルをstd::stringに読み込み、findメソッドで改行コードを先頭から検索→見つかるたびにreplaceメソッドで空文字に置換するような処理を作成しました。
元データが10万行を超えるような長いものになるととても時間がかかるのですが、より良い方法はないでしょうか。
よろしくお願いいたします。

###該当のソースコード

c++

1//あらかじめstd::string targetStrに対象の文字列が読み込まれているものとする。 2const std::string CRLF = "\r\n"; 3const std::string CR = "\r"; 4const std::string LF = "\n"; 5std::string::size_type pos = 0; 6while(pos = targetStr.find(CRLF, pos), pos != std::string::npos) { 7 targetStr.replace(pos,CRLF.length(), ""); 8 pos += CRLF.length(); 9} 10pos = 0; 11while(pos = targetStr.find(CR, pos), pos != std::string::npos) { 12 targetStr.replace(pos,CR.length(), ""); 13 pos += CR.length(); 14} 15pos = 0; 16while(pos = targetStr.find(LF, pos), pos != std::string::npos) { 17 targetStr.replace(pos,LF.length(), ""); 18 pos += LF.length(); 19}

###補足情報(言語/FW/ツール等のバージョンなど)
cまたはc++
ただしc++11, c++14の機能は使用せず実現したい

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

ベストアンサー

std::string::replaceはコストが高い処理なので、きついのだと思います。また、CRの処理とLFの処理だけでCRLFも削除されるので必要ありません。ということで、別途、stringを新たに作る形にして、最後にコピーすれば良いのでは無いでしょうか?ただし、メモリは2倍必要になるので、その点はご注意を。

C++

1void deleteNl(std::string &targetStr) 2{ 3 const char CR = '\r'; 4 const char LF = '\n'; 5 std::string destStr; 6 for (std::string::const_iterator it = targetStr.begin(); 7 it != targetStr.end(); ++it) { 8 if (*it != CR && *it != LF) { 9 destStr += *it; 10 } 11 } 12 targetStr = destStr; 13}

おまけ

C++11が使えるならムーブにすることで、ちょっとだけ速くなるような気がします。

C++

1void deleteNl2(std::string &targetStr) 2{ 3 const char CR = '\r'; 4 const char LF = '\n'; 5 std::string destStr; 6 for (const auto c : targetStr) { 7 if (c != CR && c != LF) { 8 destStr += c; 9 } 10 } 11 targetStr = std::move(destStr); 12}

投稿2016/05/08 14:49

編集2016/05/08 15:06
raccy

総合スコア21737

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

evans

2016/05/08 15:08

raccy様 ご回答ありがとうございます。 取り急ぎ試してみたところ、私のパソコンで10万行のテキストファイルを読み込むと、処理時間が2.32s→0.07s程となりました。
guest

0

C++98/03縛りでもBoostライブラリの利用OKなら、1行で実現できます。速度は未計測ですが、replaceを3回呼び出すよりは速いと期待したいところ...

C++

1#include <boost/range/algorithm_ext/erase.hpp> 2#include <boost/algorithm/string/classification.hpp> 3 4void remove_crlf(std::string& s) 5{ 6 boost::remove_erase_if(s, boost::is_any_of("\r\n")); 7}

投稿2016/05/09 01:36

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

evans

2016/05/09 15:42

yohhoy様 ご回答ありがとうございます。 外部のライブラリに任せるという選択肢が頭になかったので勉強になりました。 Wiki見てみましたが色々なことができるようですね。 ご回答ありがとうございました。
guest

0

How about this?

C++

1void remove_crlf(std::string& s) 2{ 3 size_t i, j; 4 for( i = 0, j = 0; i < s.size(); i++ ){ 5 if( s[i] != '\r' && s[i] != '\n' ){ 6 s[j] = s[i]; 7 j++; 8 } 9 } 10 s.resize(j); 11}

Without resizing, this string is not null-terminating.

投稿2016/05/09 02:36

編集2016/05/09 02:47
majiponi

総合スコア1722

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

evans

2016/05/09 15:44

Dear majiponi. Thankyou for your answering. I will use that as reference from now on.
guest

0

「CRまたはLF」を除外してみた:

C++

1#include <iostream> 2#include <string> 3#include <algorithm> 4 5using namespace std; 6 7int main() { 8 string str = "abc\r\ndef\r\nghi\r\n"; 9 string::iterator last = 10 remove_if(str.begin(), str.end(), 11 [](char ch) { return ch == '\r' || ch == '\n'; }); 12 str.erase(last, str.end()); 13 cout << '[' << str << "]\n"; 14}

投稿2016/05/09 00:07

episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

episteme

2016/05/09 01:18

lambda使うなってことでしたら struct cr_or_lf {  bool operator()(char ch) const { return ch=='\r' || ch == '\n'; } }; を用意して、lambdaんトコを cr_or_lf() にさしかえ。
evans

2016/05/09 15:39

episteme様 ご回答ありがとうございます。 参考にさせていただきます。
guest

0

10万行ともなるとコピーコストも莫迦にできませんね。文字列の途中の文字を削除しようとすると、その位置にそれ以降を文字列終端までコピーすることになり、結果、同じ部分を何度もコピーするので非効率です。
raccyさんのように別の文字列に移し替えるようにすれば最小限のコピーで済みます。

raccyさんとは別のやり方で作ってみました。文字列領域を若干多めに取ることになりますが、コピーは最小限だと思います。
※reserveで領域サイズを決め打ちしているので、もしかしたら領域の節約になっているかもしれません。

非C++11版

C++

1struct IfCrLf 2{ 3 bool operator ()(char ch) 4 { 5 return ch == '\r' || ch == '\n'; 6 } 7}; 8 9struct IfNotCrLf 10{ 11 bool operator ()(char ch) 12 { 13 return ch != '\r' && ch != '\n'; 14 } 15}; 16 17std::string str; 18str.reserve(targetStr.length()); 19auto iter = targetStr.begin(); 20while(iter != targetStr.end()) 21{ 22 auto iter2 = std::find_if(iter, targetStr.end(), IfCrLf()); 23 str.append(iter, iter2); 24 iter = std::find_if(iter2, targetStr.end(), IfNotCrLf()); 25} 26targetStr.swap(str);

C++11版

C++

1auto ifcrlf = [](char ch){return ch == '\r' || ch == '\n';}; 2std::string str; 3str.reserve(targetStr.length()); 4auto iter = targetStr.cbegin(); 5while(iter != targetStr.cend()) 6{ 7 auto iter2 = std::find_if(iter, targetStr.cend(), ifcrlf); 8 str.append(iter, iter2); 9 iter = std::find_if_not(iter2, targetStr.cend(), ifcrlf); 10} 11targetStr.swap(str);

投稿2016/05/08 15:29

編集2016/05/08 15:43
catsforepaw

総合スコア5944

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Chironian

2016/05/08 15:45

ああ、完全に思い違いしてました。「空文字に置換」って文字削除だったのですね。 それは重いはず。
evans

2016/05/09 15:39

catsforepaw様 ご回答ありがとうございます。 参考にさせていただきます。
guest

0

こんにちは。

string::find_first_of()でCRとLFの最初に出現したものをサーチしつつ、リプレースすれば1パスで済むので1.5倍強くらいの速度になるだろうと思います。


【追記】
文字は削除するだけなので、前方へコピーすれば済む筈です。

C++

1std::size_t n=0; 2std::string::iterator dst=targetStr.begin(); 3std::string::iterator end=targetStr.end(); 4for (std::string::iterator src=targetStr.begin() ; src != end; ++src) 5{ 6 if ((*src == '\r') || (*src == '\n')) 7continue; 8 9 *dst++=*src; 10 ++n; 11} 12targetStr.resize(n);

最後のresize()で変なコピーしないかちょっとだけ心配ですが、縮める方向なので管理領域を書き換えるだけと期待。

投稿2016/05/08 14:03

編集2016/05/08 16:02
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2016/05/08 14:35

つまり static const char CRLF[] = "\r\n"; for(std::size_t pos = targetStr.find_first_of(CRLF, 0, sizeof(CRLF)); std::string::npos != pos; pos = targetStr.find_first_of(CRLF, pos, sizeof(CRLF))) targetStr[pos] = '\0'; こうかな。
evans

2016/05/08 14:51

Chironian様 ご回答ありがとうございます。 以下のようなコードで認識あっておりますでしょうか? std::string::size_type pos = 0; while(pos = targetStr.find_first_of("\r\n", pos), pos != std::string::npos) { targetStr.replace(pos, 1, ""); pos += 1; } しかし上記のコードをCRLFを含むテキストファイルに試してみたところ、CRだけがreplaceされてしまいました… 使い方をもう少し勉強してみます。
evans

2016/05/08 14:54

yumetodo様 ご回答ありがとうございます。 いただいたコードでコンパイルして実行してみたのですが、どこかで止まってしまい終了しなくなりました。 ↓書いたコード全文 -------------------------------------------- #include <fstream> #include <iostream> #include <string> #include <iterator> int main() { std::ifstream ifs("test.txt"); if (ifs.fail()) { std::cerr << "失敗" << std::endl; return -1; } std::string targetStr((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); //const std::string CRLF = "\r\n"; //const std::string CR = "\r"; //const std::string LF = "\n"; static const char CRLF[] = "\r\n"; for(std::size_t pos = targetStr.find_first_of(CRLF, 0, sizeof(CRLF)); std::string::npos != pos; pos = targetStr.find_first_of(CRLF, pos, sizeof(CRLF))) targetStr[pos] = '\0'; std::cout << "[" << targetStr << "]" << std::endl; return 0; }
Chironian

2016/05/08 15:05

evansさん。 replace()が悪さしているようです。targetStr[pos]=0;にすればちゃんと0に置き換えられました。また、raccyさんによるとreplaceは遅いようなので、targetStr[pos]の方が速いと思います。(operator[]は指定文字への参照を返却しますので変更可能です。) yumetodoさん。 sizeof(CRLF)は3になるのでちょっとまずいかも。
evans

2016/05/08 15:24

Chironian様 どうもありがとうございます。 targetStr.replace(pos, 1, ""); を targetStr[pos]=0; にしたところ、意図したとおりに動作しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問