ProgramingTip

std :: string을 다듬는 가장 좋은 방법은 무엇입니까?

bestdevel 2020. 9. 29. 08:13
반응형

std :: string을 다듬는 가장 좋은 방법은 무엇입니까?


현재 다음 코드를 사용하여 std::strings내 프로그램의 모든 항목을 오른쪽 트리밍합니다 .

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

잘 작동하지만 실패 할 수있는 최종 사례가 있는지 궁금합니다.

물론 우아한 대안과 제안 솔루션에 대한 답변도 환영합니다.


편집 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 가지 중 하나를 사용하는 경향이 있습니다.

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start
static inline std::string &ltrim(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));
}

그들은 상당히 자명하고 잘 작동합니다.

편집 : BTW, 실제로 로케일을 지원하는 두 번째 정의가 있기 때문에 std::ptr_fun쉽게 찾을 수 std::isspace있습니다. 이 똑같은 캐스트가 될 수 있습니다 나는 더 좋아합니다.

편집 : 참조로 매개 변수를 받아들이고 수정하고 반환하는 것에 대한 몇 가지 의견을 해결합니다. 동의한다. 내가 선호하는 구현은 두 세트의 함수, 하나는 제자리에 있고 다른 하나는 복사본을 만드는 것입니다. 더 나은 예는 다음과 가변적입니다.

#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;
}

위의 원래 답변은 맥락과 높은 투표를 계속 사용할 수 있습니다. 유지하기 위해 유지하고 있습니다.


사용 부스트의 높은 알고리즘은 쉬운 것입니다 :

#include <boost/algorithm/string.hpp>

std::string str("hello world! ");
boost::trim_right(str);

str지금 "hello world!"입니다. 양쪽을 다듬는 trim_lefttrim있습니다.


_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두 번째에서 종료 조건으로 사용할 수 find_if_not있습니다. std::string::const_reverse_iterator)와 auto. 역방향 반복기를 만드는 데 얼마나 많은 비용이 있는지 모르겠습니다. 그래서 여기에 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;

http://ideone.com/nFVtEo

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());
}

빈 호스팅의 경우 코드는 1을 추가하여 string::npos0 제공 합니다 가정합니다 . 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에반의 대답을 사용하여 언어 표현이없는 문자에 그래픽 표현이 사용 가능합니다. 결과는 훨씬 더 우아한 솔루션입니다.

#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::iswgraphstd::wstringstd::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 :: finds를 사용하는 대신 사용하는 대신 사용합니다. 다음과 같은 특수한 경우를 최적화합니다 : 크기가 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 ++ 17에서는 basic_string_view :: remove_prefixbasic_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 ++ 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());
}

업그레이드를 다듬는 "최선의 방법"을 요청하기 시작하면 다음과 같은 좋은 구현이 될 것입니다.

  1. 임시 디렉토리를 할당하지 않습니다.
  2. 내부 언어 및 음악에 대한 문구가 있습니다.
  3. 다양한 시퀀스 시퀀스 / 수용을 수용 가능한 쉽게 사용자 정의 가능

분명히 이것에 접근하는 데는 너무 많은 다른 방법이 설치되어 있습니다. 그러나 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;
}

귀하의 환경이 동일한 지 확실하지 않지만 내 경우에 빈번하게 발생하는 프로그램이 중단됩니다. 그 지우기 호출을 if (! s.empty ())로 사용하거나 이미 언급했듯이 Boost를 사용합니다.


내가 생각하는 것은 다음과 가변적이다.

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

스트림 추출은 공백을 자동으로 제거 할 때 매력처럼 작동합니다.
내가 그렇게 말하면 꽤 깨끗하고 우아합니다. ;)


소음에 대한 내 솔루션을 제공합니다. 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");  
}

이 작업은 추가로 인해 C ++ 11에서 더 간단하게 수행 할 수 있습니다 .back()pop_back()

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 버전에서 복사 한 약간의 수정 된 기능이 있습니다. 이 예에서는 영숫자가 아닌 문자를 잘라냅니다.

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);
}

다음은 간단한 구현입니다. 가장 간단한 작업을 위해 특수 구조를 사용합니다. 내장 된 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);
}

여기에 std::모든 곳 에서 익숙하지 않고 아직-정확함 const, iterators, STL을 사용 algorithm하지 않고 쉽게 이해하기 쉬운 솔루션이 있습니다 .

#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));
}

도움이 되셨기를 바랍니다 ...


이 버전은 내부 공백과 영숫자가 아닌 문자를 제거합니다.

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;
}

또 다른 옵션-양쪽 끝에서 하나 이상의 문자를 제거합니다.

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);
}

참고 URL : https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring

반응형