私は現在、私のプログラムのすべてのstd::strings
を右トリムするために次のコードを使用しています。
std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);
うまくいきますが、失敗する可能性がある最終的なケースがあるのでしょうか。
もちろん、洗練された代替案や左揃えの解決策での回答も大歓迎です。
_ edit _ c ++ 17以降、標準ライブラリの一部が削除されました。幸いなことに、c ++ 11から始めて、私たちは優れた解決策であるラムダを持っています。
#include <algorithm>
#include <cctype>
#include <locale>
// trim from start (in place)
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
trim(s);
return s;
}
https://stackoverflow.com/a/44973498/524503 に近代的な解決策をもたらしてくれてありがとう。
私は私のトリミングニーズにこれら3つのうちの1つを使用する傾向があります。
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
// trim from start
static inline std::string <rim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
return s;
}
// trim from end
static inline std::string &rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}
// trim from both ends
static inline std::string &trim(std::string &s) {
return ltrim(rtrim(s));
}
それらはかなり自明で、とてもうまく機能します。
_ edit _ :ところで、ロケールをサポートする2番目の定義が実際にあるのでstd::ptr_fun
を明確にするのを助けるためにそこにstd::isspace
があります。これはまったく同じキャストである可能性がありますが、私はこれを好む傾向があります。
_ edit _ :参照によるパラメータの受け入れ、変更および返却に関するコメントに対処する。同意する。私が好むと思う実装は2組の機能、その1つはインプレース用、もう1つはコピーを作成することです。より良い例は次のようになります。
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
// trim from start (in place)
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
trim(s);
return s;
}
私は上記の元の答えを文脈のためにそして高投票の答えがまだ利用可能であり続けることの利益のために保っています。
Boostの文字列アルゴリズム を使うのが最も簡単でしょう。
#include <boost/algorithm/string.hpp>
std::string str("hello world! ");
boost::trim_right(str);
str
は"hello world!"
になりました。両側をトリミングするtrim_left
とtrim
もあります。
上記の関数名のいずれかに_copy
サフィックスを追加すると、 trim_copy
は、関数は参照を通してそれを修正する代わりに文字列のトリミングされたコピーを返します。
上記の関数名のいずれかに_if
サフィックスを追加すると、 trim_copy_if
では、空白だけではなく、カスタム述語を満たすすべての文字をトリミングできます。
次のコードを使用して、std::strings
( ideone )のスペースとタブ文字を右端(末尾)にトリミングします。
// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
str = str.substr( 0, endpos+1 );
str = str.substr( startpos );
}
else {
str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}
バランスをとるために、左側のトリムコードも含めます( ideone )。
// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
str = str.substr( startpos );
}
パーティーに遅刻しますが、気にしないでください。 C++ 11ができました。ラムダと自動変数があります。それで私のバージョンは全空白文字と空の文字列も扱います:
#include <cctype>
#include <string>
#include <algorithm>
inline std::string trim(const std::string &s)
{
auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}
wsfront
から逆方向反復子を作成し、それを2番目のfind_if_not
の終了条件として使用できますが、それは全空白文字列の場合にのみ有用であり、gcc 4.8は逆方向の型を推測するのに十分賢くありません。 auto
を持つイテレータ(std::string::const_reverse_iterator
)逆イテレータを作成するのにかかる費用がわからないので、ここではYMMVを使用します。この変更により、コードは次のようになります。
inline std::string trim(const std::string &s)
{
auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}
あなたがしていることは、元気で頑強です。私は長い間同じ方法を使用してきましたが、もっと早い方法をまだ見つけていません。
const char* ws = " \t\n\r\f\v";
// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
s.erase(s.find_last_not_of(t) + 1);
return s;
}
// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
s.erase(0, s.find_first_not_of(t));
return s;
}
// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
return ltrim(rtrim(s, t), t);
}
切り捨てられる文字を指定することで、空白以外の文字を切り捨てる柔軟性と、切り取りたい文字だけを切り取る効率が得られます。
これを試して、それは私のために働く。
inline std::string trim(std::string& str)
{
str.erase(0, str.find_first_not_of(' ')); //prefixing spaces
str.erase(str.find_last_not_of(' ')+1); //surfixing spaces
return str;
}
私はtzamanの解決策が好きです、それに関する唯一の問題はそれがスペースだけを含む文字列をトリムしないということです。
この1つの欠陥を修正するには、2つのトリマーラインの間にstr.clear()を追加します。
std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
std::string trim(const std::string &s)
{
std::string::const_iterator it = s.begin();
while (it != s.end() && isspace(*it))
it++;
std::string::const_reverse_iterator rit = s.rbegin();
while (rit.base() != it && isspace(*rit))
rit++;
return std::string(it, rit.base());
}
空の文字列の場合、コードはstring::npos
に1を加えると0になると想定します。string::npos
はstring::size_type
型で、符号なしです。したがって、加算のオーバーフロー動作に頼っています。
Cplusplus.com のハッキング
std::string choppa(const std::string &t, const std::string &ws)
{
std::string str = t;
size_t found;
found = str.find_last_not_of(ws);
if (found != std::string::npos)
str.erase(found+1);
else
str.clear(); // str is all whitespace
return str;
}
これはnullの場合にも同様に機能します。 :-)
私の解決策は に基づいて@Bill the Lizard 。
入力文字列に空白以外のものが含まれていない場合、これらの関数は空の文字列を返します。
const std::string StringUtils::WHITESPACE = " \n\r\t";
std::string StringUtils::Trim(const std::string& s)
{
return TrimRight(TrimLeft(s));
}
std::string StringUtils::TrimLeft(const std::string& s)
{
size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
return (startpos == std::string::npos) ? "" : s.substr(startpos);
}
std::string StringUtils::TrimRight(const std::string& s)
{
size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}
私の答えは、この投稿の トップの回答 を改善したものです。これは、コントロール文字とスペース( ASCIIテーブルでは0-32と127 )を削除します。
std::isgraph
は文字がグラフィック表現を持つかどうかを決定するので、これを使って文字列の両側からグラフィック表現を持たない文字を削除するためにEvanの答えを変更することができます。その結果、はるかに洗練された解決策が得られます。
#include <algorithm>
#include <functional>
#include <string>
/**
* @brief Left Trim
*
* Trims whitespace from the left end of the provided std::string
*
* @param[out] s The std::string to trim
*
* @return The modified std::string&
*/
std::string& ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::ptr_fun<int, int>(std::isgraph)));
return s;
}
/**
* @brief Right Trim
*
* Trims whitespace from the right end of the provided std::string
*
* @param[out] s The std::string to trim
*
* @return The modified std::string&
*/
std::string& rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
return s;
}
/**
* @brief Trim
*
* Trims whitespace from both ends of the provided std::string
*
* @param[out] s The std::string to trim
*
* @return The modified std::string&
*/
std::string& trim(std::string& s) {
return ltrim(rtrim(s));
}
注: ワイド文字のサポートが必要な場合は std::iswgraph
を使用することもできますが、std::wstring
操作を有効にするにはこのコードを編集する必要があります。テストされていません(このオプションについては、 std::basic_string
の参照ページを参照してください)。
C++ 11には 正規表現 モジュールも付属していました。もちろん、これを使用して先頭または末尾のスペースを削除することができます。
多分このようなもの:
std::string ltrim(const std::string& s)
{
static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
return std::regex_replace(s, lws, "");
}
std::string rtrim(const std::string& s)
{
static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
return std::regex_replace(s, tws, "");
}
std::string trim(const std::string& s)
{
return ltrim(rtrim(s));
}
これが私が使っているものです。正面からスペースを取り去り、その後、何か残っている場合は、背面から同じようにします。
void trim(string& s) {
while(s.compare(0,1," ")==0)
s.erase(s.begin()); // remove leading whitespaces
while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
s.erase(s.end()-1); // remove trailing whitespaces
}
その価値があるのは、パフォーマンスを見据えたトリム実装です。それは私が周りで見た他の多くのトリムルーチンよりはるかに速いです。イテレータとstd :: findを使う代わりに、生のc文字列とインデックスを使います。次の特殊なケースを最適化します。size 0文字列(何もしない)、空白を含まない文字列(何もしない)、末尾の空白文字のみを含む文字列(文字列のサイズを変更する)、完全に空白の文字列(文字列をクリアする) 。そして最後に、最悪の場合(先行空白を含む文字列)、効率的なコピー構築を行い、1回のコピーだけを実行してから元の文字列の代わりにそのコピーを移動するのが最善です。
void TrimString(std::string & str)
{
if(str.empty())
return;
const auto pStr = str.c_str();
size_t front = 0;
while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}
size_t back = str.length();
while(back > front && std::isspace(int(pStr[back-1]))) {--back;}
if(0 == front)
{
if(back < str.length())
{
str.resize(back - front);
}
}
else if(back <= front)
{
str.clear();
}
else
{
str = std::move(std::string(str.begin()+front, str.begin()+back));
}
}
s.erase(0, s.find_first_not_of(" \n\r\t"));
s.erase(s.find_last_not_of(" \n\r\t")+1);
それをする優雅な方法はのようである場合もあります
std::string & trim(std::string & str)
{
return ltrim(rtrim(str));
}
そして、補助機能は以下のように実装されています。
std::string & ltrim(std::string & str)
{
auto it = std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
str.erase( str.begin() , it);
return str;
}
std::string & rtrim(std::string & str)
{
auto it = std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
str.erase( it.base() , str.end() );
return str;
}
そして、これらがすべて整ったら、これも書くことができます。
std::string trim_copy(std::string const & str)
{
auto s = str;
return ltrim(rtrim(s));
}
C++ 11の実装をトリミングします。
static void trim(std::string &s) {
s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}
C++ 17では、 basic_string_view :: remove_prefix および basic_string_view :: remove_suffix を使用できます。
std::string_view trim(std::string_view s) const
{
s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
s.remove_suffix((s.size() - 1) - std::min(s.find_last_not_of(" \t\r\v\n"), s.size() - 1));
return s;
}
あなたが文字列をトリミングするための「最善の方法」を求め始めたら、私は良い実装がそれであると言うでしょう:
明らかにこれに近づくには多種多様な方法があり、それは間違いなくあなたが実際に必要なものに依存します。しかし、C標準ライブラリには、memchrのように、<string.h>にまだ非常に便利な関数がいくつかあります。 Cが依然としてIOの最良の言語と見なされているのには理由があります - そのstdlibは純粋な効率です。
inline const char* trim_start(const char* str)
{
while (memchr(" \t\n\r", *str, 4)) ++str;
return str;
}
inline const char* trim_end(const char* end)
{
while (memchr(" \t\n\r", end[-1], 4)) --end;
return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
str.assign(trim_start(str.c_str()),
trim_end(str.c_str() + str.length()));
}
int main()
{
char str [] = "\t \nhello\r \t \n";
string trimmed = trim(str, strlen(str));
cout << "'" << trimmed << "'" << endl;
system("pause");
return 0;
}
騒音に対する私の解決策に貢献してください。 trim
はデフォルトで新しい文字列を作成し、変更された文字列を返すのに対してtrim_in_place
は渡された文字列を変更します。 trim
関数は、c ++ 11の移動セマンティクスをサポートしています。
#include <string>
// modifies input string, returns input
std::string& trim_left_in_place(std::string& str) {
size_t i = 0;
while(i < str.size() && isspace(str[i])) { ++i; };
return str.erase(0, i);
}
std::string& trim_right_in_place(std::string& str) {
size_t i = str.size();
while(i > 0 && isspace(str[i - 1])) { --i; };
return str.erase(i, str.size());
}
std::string& trim_in_place(std::string& str) {
return trim_left_in_place(trim_right_in_place(str));
}
// returns newly created strings
std::string trim_right(std::string str) {
return trim_right_in_place(str);
}
std::string trim_left(std::string str) {
return trim_left_in_place(str);
}
std::string trim(std::string str) {
return trim_left_in_place(trim_right_in_place(str));
}
#include <cassert>
int main() {
std::string s1(" \t\r\n ");
std::string s2(" \r\nc");
std::string s3("c \t");
std::string s4(" \rc ");
assert(trim(s1) == "");
assert(trim(s2) == "c");
assert(trim(s3) == "c");
assert(trim(s4) == "c");
assert(s1 == " \t\r\n ");
assert(s2 == " \r\nc");
assert(s3 == "c \t");
assert(s4 == " \rc ");
assert(trim_in_place(s1) == "");
assert(trim_in_place(s2) == "c");
assert(trim_in_place(s3) == "c");
assert(trim_in_place(s4) == "c");
assert(s1 == "");
assert(s2 == "c");
assert(s3 == "c");
assert(s4 == "c");
}
あなたの環境が同じであるかどうか私にはわかりませんが、私の考えでは、空文字列の場合はプログラムが中断されます。その消去呼び出しをif(!s.empty())でラップするか、既に述べたようにBoostを使用します。
これが私が思いついたものです:
std::stringstream trimmer;
trimmer << str;
trimmer >> str;
ストリーム抽出は空白を自動的に削除するので、これは魅力的なように機能します。
私がそう言うのであれば、かなり清潔でエレガントでもあります。 ;)
back()
および pop_back()
が追加されているため、C++ 11ではこれをより簡単に行うことができます。
while ( !s.empty() && isspace(s.back()) ) s.pop_back();
これが私のバージョンです:
size_t beg = s.find_first_not_of(" \r\n");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);
上記の方法は素晴らしいですが、ルーチンが空白と見なすために関数の組み合わせを使いたいことがあります。この場合、ファンクタを使って操作を組み合わせるのは面倒になることがあるので、私はトリム用に変更できる単純なループを好みます。これは、CバージョンのSOからコピーした、少し修正したトリム関数です。この例では、英数字以外の文字をトリミングしています。
string trim(char const *str)
{
// Trim leading non-letters
while(!isalnum(*str)) str++;
// Trim trailing non-letters
end = str + strlen(str) - 1;
while(end > str && !isalnum(*end)) end--;
return string(str, end+1);
}
このバージョンでは、内部の空白文字と英数字以外の文字が削除されます。
static inline std::string &trimAll(std::string &s)
{
if(s.size() == 0)
{
return s;
}
int val = 0;
for (int cur = 0; cur < s.size(); cur++)
{
if(s[cur] != ' ' && std::isalnum(s[cur]))
{
s[val] = s[cur];
val++;
}
}
s.resize(val);
return s;
}
さらに別のオプション - 両端から1つ以上の文字を削除します。
string strip(const string& s, const string& chars=" ") {
size_t begin = 0;
size_t end = s.size()-1;
for(; begin < s.size(); begin++)
if(chars.find_first_of(s[begin]) == string::npos)
break;
for(; end > begin; end--)
if(chars.find_first_of(s[end]) == string::npos)
break;
return s.substr(begin, end-begin+1);
}
これは、どこにでもstd::
を書くのに慣れておらず、まだconst
-correctness、iterator
s、STL algorithm
sなどに慣れていない初心者にとって理解しやすいソリューションです。
#include <string>
#include <cctype> // for isspace
using namespace std;
// Left trim the given string (" hello! " --> "hello! ")
string left_trim(string str) {
int numStartSpaces = 0;
for (int i = 0; i < str.length(); i++) {
if (!isspace(str[i])) break;
numStartSpaces++;
}
return str.substr(numStartSpaces);
}
// Right trim the given string (" hello! " --> " hello!")
string right_trim(string str) {
int numEndSpaces = 0;
for (int i = str.length() - 1; i >= 0; i--) {
if (!isspace(str[i])) break;
numEndSpaces++;
}
return str.substr(0, str.length() - numEndSpaces);
}
// Left and right trim the given string (" hello! " --> "hello!")
string trim(string str) {
return right_trim(left_trim(str));
}
それが役に立てば幸い...
私はC++ 11のアプローチで私の古いC++トリム関数を更新したいので、私は質問に対して投稿された答えの多くをテストしました。私の結論は、私は私の古いC++ソリューションを維持するということです!
これは概して最速のものであり、チェックする文字をさらに追加しても(例:\ r\n\f\vの使用例は見当たりません)、アルゴリズムを使用するソリューションよりもまだ高速です。
std::string & trimMe (std::string & str)
{
// right trim
while (str.length () > 0 && (str [str.length ()-1] == ' ' || str [str.length ()-1] == '\t'))
str.erase (str.length ()-1, 1);
// left trim
while (str.length () > 0 && (str [0] == ' ' || str [0] == '\t'))
str.erase (0, 1);
return str;
}
これはどうですか...?
#include <iostream>
#include <string>
#include <regex>
std::string ltrim( std::string str ) {
return std::regex_replace( str, std::regex("^\\s+"), std::string("") );
}
std::string rtrim( std::string str ) {
return std::regex_replace( str, std::regex("\\s+$"), std::string("") );
}
std::string trim( std::string str ) {
return ltrim( rtrim( str ) );
}
int main() {
std::string str = " \t this is a test string \n ";
std::cout << "-" << trim( str ) << "-\n";
return 0;
}
注意:私はまだC++に比較的慣れていません。
これは簡単な実装です。このような単純な操作では、おそらく特別な構成要素を使用してはいけません。組み込みのisspace()関数はさまざまな形式の白文字を扱いますので、それを利用する必要があります。また、文字列が空であるか、単なるスペースの集まりであるという特別な場合も考慮する必要があります。左または右のトリムは、次のコードから導き出すことができます。
string trimSpace(const string &str) {
if (str.empty()) return str;
string::size_type i,j;
i=0;
while (i<str.size() && isspace(str[i])) ++i;
if (i == str.size())
return string(); // empty string
j = str.size() - 1;
//while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
while (isspace(str[j])) --j
return str.substr(i, j-i+1);
}
私はこれが非常に古い質問であることを知っています、しかし私はあなたのものに数行のコードを加えました、そしてそれは両端から空白を取り除きます。
void trim(std::string &line){
auto val = line.find_last_not_of(" \n\r\t") + 1;
if(val == line.size() || val == std::string::npos){
val = line.find_first_not_of(" \n\r\t");
line = line.substr(val);
}
else
line.erase(val);
}
c ++ 11:
int i{};
string s = " h e ll \t\n o";
string trim = " \n\t";
while ((i = s.find_first_of(trim)) != -1)
s.erase(i,1);
cout << s;
出力:
hello
空の文字列でもうまく動作する
下記は1パス(2パスかもしれません)の解決策です。文字列の空白部分を2回、空白以外の部分を1回超えます。
void trim(std::string& s) {
if (s.empty())
return;
int l = 0, r = s.size() - 1;
while (l < s.size() && std::isspace(s[l++])); // l points to first non-whitespace char.
while (r >= 0 && std::isspace(s[r--])); // r points to last non-whitespace char.
if (l > r)
s = "";
else {
l--;
r++;
int wi = 0;
while (l <= r)
s[wi++] = s[l++];
s.erase(wi);
}
return;
}
これでいいの? (この投稿は完全に別の答えが必要です:)
string trimBegin(string str)
{
string whites = "\t\r\n ";
int i = 0;
while (whites.find(str[i++]) != whites::npos);
str.erase(0, i);
return str;
}
TrimEndの場合も同じように、偏光子のインデックスを逆にします。
私はこれを使っています:
void trim(string &str){
int i=0;
//left trim
while (isspace(str[i])!=0)
i++;
str = str.substr(i,str.length()-i);
//right trim
i=str.length()-1;
while (isspace(str[i])!=0)
i--;
str = str.substr(0,i+1);
}
std::string trim( std::string && str )
{
size_t end = str.find_last_not_of( " \n\r\t" );
if ( end != std::string::npos )
str.resize( end + 1 );
size_t start = str.find_first_not_of( " \n\r\t" );
if ( start != std::string::npos )
str = str.substr( start );
return std::move( str );
}