【问题标题】:How to strip all non alphanumeric characters from a string in c++?如何从 C++ 中的字符串中去除所有非字母数字字符?
【发布时间】:2011-09-13 06:35:55
【问题描述】:

我正在编写一个软件,它需要我使用 libcurl 处理从网页获取的数据。当我得到数据时,由于某种原因,它有额外的换行符。我需要想办法只允许字母、数字和空格。并删除其他所有内容,包括换行符。有什么简单的方法可以做到这一点?谢谢。

【问题讨论】:

  • 您如何存储数据?在char 缓冲区或string?

标签: c++ string libcurl strip alphanumeric


【解决方案1】:

编写一个函数,接收 char 并返回 true(如果要删除该字符)或 false(如果要保留它):

bool my_predicate(char c);

然后使用std::remove_if算法从字符串中去除不需要的字符:

std::string s = "my data";
s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());

根据您的要求,您可以使用标准库谓词之一,例如std::isalnum,而不是编写自己的谓词(您说您需要匹配字母数字字符和空格,所以也许这不会完全符合您的需要)。

如果您想使用标准库 std::isalnum 函数,您需要进行强制转换以消除 C 标准库标头 <cctype>(这是您要使用的那个)中的 std::isalnum 函数和std::isalnum 在 C++ 标准库标头 <locale> 中(这不是您要使用的,除非您要执行特定于语言环境的字符串处理):

s.erase(std::remove_if(s.begin(), s.end(), (int(*)(int))std::isalnum), s.end());

这同样适用于任何序列容器(包括std::stringstd::vectorstd::deque)。这个成语通常被称为“擦除/删除”成语。 std::remove_if 算法也适用于普通数组。 std::remove_if 只对序列进行一次传递,因此它具有线性时间复杂度。

【讨论】:

  • @James:它正在删除字母数字字符而不是特殊字符。我做错了吗?
  • 它将删除字母数字字符而不是特殊字符,因为(int(*)(int))std::isalnum 将在遇到字母数字字符时返回true,并且该字符将从字符串中删除。
  • (int(*)(int))std::isalnum 将只保留特殊字符,而是使用std::not1(std::ptr_fun( (int(*)(int))std::isalnum )) 反转其逻辑
  • 如前所述,这将删除字母数字字符,需要反转
【解决方案2】:

如果不传递 unary 参数,以前使用 std::isalnum 将无法使用 std::ptr_fun 进行编译,因此这个带有 lambda 函数的解决方案应该封装正确的答案:

s.erase(std::remove_if(s.begin(), s.end(), 
[]( auto const& c ) -> bool { return !std::isalnum(c); } ), s.end());

【讨论】:

  • 为什么需要在 auto 中包含 &c,为什么不只包含 c?
  • 是的,你可以有你想要的签名,你可以使用一个值,一个值和一个 std::move,一个完美的转发等等......我认为 auto const& 是不知道的更安全的赌注保证没有额外昂贵的副本的真实类型,尽管在相同的情况下,值/移动的性能更高。在相同的情况下,甚至是内在类型的简单值。
【解决方案3】:

如果你使用string,你总是可以循环遍历所有非字母数字字符erase

#include <cctype>

size_t i = 0;
size_t len = str.length();
while(i < len){
    if (!isalnum(str[i]) || str[i] == ' '){
        str.erase(i,1);
        len--;
    }else
        i++;
}

使用标准库更好的人可能可以在没有循环的情况下做到这一点。

如果您只使用char 缓冲区,则可以循环遍历,如果字符不是字母数字,则将其后的所有字符向后移动一个(以覆盖有问题的字符):

#include <cctype>

size_t buflen = something;
for (size_t i = 0; i < buflen; ++i)
    if (!isalnum(buf[i]) || buf[i] != ' ')
        memcpy(buf[i], buf[i + 1], --buflen - i);

【讨论】:

  • 消除循环将涉及erase-remove idiom
  • 在第二种情况下,如果您维护源和目标指针,则可以避免每次需要删除字符时对剩余缓冲区进行 memcpy。即 for (char *s = buf, *d = buf; *s; ++s) { if (!isalnum(*s) || *s != ' ') *d++ = *s; } *d = 0;
【解决方案4】:

只是对 James McNellis 的代码进行了更多扩展。他的功能是删除alnum字符而不是非alnum字符。

从字符串中删除非alnum字符。 (alnum = 字母或数字)

  • 声明一个函数(如果传递的char不是alnum,isalnum返回0)

    bool isNotAlnum(char c) {
        return isalnum(c) == 0;
    }
    
  • 然后写这个

    s.erase(remove_if(s.begin(), s.end(), isNotAlnum), s.end());
    

那么你的字符串只有 alnum 字符。

【讨论】:

    【解决方案5】:

    remove_copy_if 标准算法非常适合您的情况。

    【讨论】:

      【解决方案6】:
      #include <cctype>
      #include <string>
      #include <functional>
      
      std::string s = "Hello World!";
      s.erase(std::remove_if(s.begin(), s.end(),
          std::not1(std::ptr_fun(std::isalnum)), s.end()), s.end());
      std::cout << s << std::endl;
      

      结果:

      "HelloWorld"
      

      您使用isalnum 来确定每个字符是否是字母数字,然后使用ptr_fun 将函数传递给not1,这不是返回值,只留下您想要的字母数字内容。

      【讨论】:

        【解决方案7】:

        对不同方法进行基准测试。

        如果您正在寻找我制定的基准。

        (115830 cycles) 115.8ms -> using stringstream
        ( 40434 cycles)  40.4ms -> s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return !isalnum(c); }), s.end());
        ( 40389 cycles)  40.4ms -> s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return ispunct(c); }), s.end());
        ( 42386 cycles)  42.4ms -> s.erase(remove_if(s.begin(), s.end(), not1(ptr_fun( (int(*)(int))isalnum ))), s.end());
        ( 42969 cycles)  43.0ms -> s.erase(remove_if(s.begin(), s.end(), []( auto const& c ) -> bool { return !isalnum(c); } ), s.end());
        ( 44829 cycles)  44.8ms -> alnum_from_libc(s) see below
        ( 24505 cycles)  24.5ms -> Puzzled? My method, see below
        (  9717 cycles)   9.7ms -> using mask and bitwise operators
        
        Original length: 8286208, current len with alnum only: 5822471
        

        • Stringstream 的结果很糟糕(但我们都知道)
        • 已经给出的不同答案给出了相同的运行时间
        • 以 C 方式执行它始终可以提供更好的运行时间(几乎快两倍!),这绝对值得考虑,而且它与 C 语言兼容。
        • 我的按位方法(也与 C 兼容)速度提高了 400% 以上。

        注意,必须修改所选答案,因为它只保留特殊字符

        NB2:测试文件是一个(几乎)8192 kb 的文本文件,包含大约 62 个 alnum 和 12 个特殊字符,随机且均匀地写入。


        基准测试源代码

        #include <ctime>
        
        #include <iostream>
        #include <sstream>
        #include <string>
        #include <algorithm>
        
        #include <locale> // ispunct
        #include <cctype>
        
        #include <fstream> // read file
        #include <streambuf>
        
        #include <sys/stat.h> // check if file exist
        #include <cstring>
        
        using namespace std;
        
        bool exist(const char *name)
        {
          struct stat   buffer;
          return !stat(name, &buffer);
        }
        
        constexpr int SIZE = 8092 * 1024;
        
        void keep_alnum(string &s) {
            stringstream ss;
            int i = 0;
            for (i = 0; i < SIZE; i++)
                if (isalnum(s[i]))
                    ss << s[i];
            s = ss.str();
        }
        
        /* my method, best runtime */
        void old_school(char *s) {
            int n = 0;
            for (int i = 0; i < SIZE; i++) {
                unsigned char c = s[i] - 0x30; // '0'
                if (c < 10 || (c -= 0x11) < 26 || (c -= 0x20) < 26) // 0x30 + 0x11 = 'A' + 0x20 = 'a'
                    s[n++] = s[i];
            }
            s[n] = '\0';
        }
        
        void alnum_from_libc(char *s) {
            int n = 0;
            for (int i = 0; i < SIZE; i++) {
                if (isalnum(s[i]))
                    s[n++] = s[i];
            }
            s[n] = '\0';
        }
        
        #define benchmark(x) printf("\033[30m(%6.0lf cycles) \033[32m%5.1lfms\n\033[0m", x, x / (CLOCKS_PER_SEC / 1000))
        
        int main(int ac, char **av) {
            if (ac < 2) {
                cout << "usage: ./a.out \"{your string} or ./a.out FILE \"{your file}\n";
                return 1;
            }
            string s;
            s.reserve(SIZE+1);
            string s1;
            s1.reserve(SIZE+1);
            char s4[SIZE + 1], s5[SIZE + 1];
            if (ac == 3) { 
                if (!exist(av[2])) {
                    for (size_t i = 0; i < SIZE; i++)
                      s4[i] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnoporstuvwxyz!@#$%^&*()__+:\"<>?,./'"[rand() % 74];
                  s4[SIZE] = '\0';
                  ofstream ofs(av[2]);
                  if (ofs)
                    ofs << s4;
                }
                ifstream ifs(av[2]);
                if (ifs) {
                  ifs.rdbuf()->pubsetbuf(s4, SIZE);
                  copy(istreambuf_iterator<char>(ifs), {}, s.begin());
                }
                else
                  cout << "error\n";
        
                ifs.seekg(0, ios::beg);
                s.assign((istreambuf_iterator<char>(ifs)), istreambuf_iterator<char>());
            }
            else
                s = av[1];
        
            double elapsedTime;
            clock_t start;
            bool is_different = false;
        
            s1 = s;
            start = clock();
            keep_alnum(s1);
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            string tmp = s1;
        
            s1 = s;
            start = clock();
            s1.erase(std::remove_if(s1.begin(), s1.end(), [](char c) { return !isalnum(c); }), s1.end());
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            is_different |= !!strcmp(tmp.c_str(), s1.c_str());
        
            s1 = s;
            start = clock();
            s1.erase(std::remove_if(s1.begin(), s1.end(), [](char c) { return ispunct(c); }), s1.end());
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            is_different |= !!strcmp(tmp.c_str(), s1.c_str());
        
            s1 = s;
            start = clock();
            s1.erase(remove_if(s1.begin(), s1.end(), not1(ptr_fun( (int(*)(int))isalnum ))), s1.end());
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            is_different |= !!strcmp(tmp.c_str(), s1.c_str());
        
            s1 = s;
            start = clock();
            s1.erase(remove_if(s1.begin(), s1.end(), []( auto const& c ) -> bool { return !isalnum(c); } ), s1.end());
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            is_different |= !!strcmp(tmp.c_str(), s1.c_str());
        
            memcpy(s4, s.c_str(), SIZE);
            start = clock();
            alnum_from_libc(s4);
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            is_different |= !!strcmp(tmp.c_str(), s4);
        
            memcpy(s4, s.c_str(), SIZE);
            start = clock();
            old_school(s4);
            elapsedTime = (clock() - start);
            benchmark(elapsedTime);
            is_different |= !!strcmp(tmp.c_str(), s4);
        
            cout << "Original length: " << s.size() << ", current len with alnum only: " << strlen(s4) << endl;
            // make sure that strings are equivalent
            printf("\033[3%cm%s\n", ('3' + !is_different), !is_different ? "OK" : "KO");
        
            return 0;
        }
        

        我的解决方案

        对于按位方法,您可以直接在my github 上检查它,基本上我避免了分支指令(如果),这要归功于掩码。我避免使用 C++ 标签发布按位运算,我对此非常讨厌。

        对于 C 风格的一种,我遍历字符串并有两个索引:n 用于我们保留的字符,i 用于遍历字符串,如果它是数字,我们会依次测试大写或小写。

        添加此功能:

        void strip_special_chars(char *s) {
            int n = 0;
            for (int i = 0; i < SIZE; i++) {
                unsigned char c = s[i] - 0x30;
                if (c < 10 || (c -= 0x11) < 26 || (c -= 0x20) < 26) // 0x30 + 0x11 = 'A' + 0x20 = 'a'
                    s[n++] = s[i];
            }
            s[n] = '\0';
        }
        

        并用作:

        char s1[s.size() + 1]
        memcpy(s1, s.c_str(), s.size());
        strip_special_chars(s1);
        

        【讨论】:

          【解决方案8】:

          您可以通过这种方式使用删除-擦除算法 -

          // Removes all punctuation       
          s.erase( std::remove_if(s.begin(), s.end(), &ispunct), s.end());
          

          【讨论】:

            【解决方案9】:

            对于给定的字符串s,下面的代码应该可以正常工作。它使用了&lt;algorithm&gt;&lt;locale&gt; 库。

            std::string s("He!!llo  Wo,@rld! 12 453");
            s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return !std::isalnum(c); }), s.end());
            

            【讨论】:

              【解决方案10】:

              提到的解决方案

              s.erase( std::remove_if(s.begin(), s.end(), &std::ispunct), s.end());
              

              非常好,但不幸的是在 Visual Studio(调试模式)中不能使用像“Ñ”这样的字符,因为这行:

              _ASSERTE((unsigned)(c + 1) <= 256)
              

              在 isctype.c 中

              所以,我会推荐这样的东西:

              inline int my_ispunct( int ch )
              {
                  return std::ispunct(unsigned char(ch));
              }
              ...
              s.erase( std::remove_if(s.begin(), s.end(), &my_ispunct), s.end());
              

              【讨论】:

                【解决方案11】:

                以下对我有用。

                str.erase(std::remove_if(str.begin(), str.end(), &ispunct), str.end());
                str.erase(std::remove_if(str.begin(), str.end(), &isspace), str.end());
                

                【讨论】:

                  【解决方案12】:
                  void remove_spaces(string data)
                  { int i=0,j=0;
                      while(i<data.length())
                      {
                          if (isalpha(data[i]))
                          {
                          data[i]=data[i];
                          i++;
                          }
                          else
                              {
                              data.erase(i,1);}
                      }
                      cout<<data;
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2015-02-26
                    • 1970-01-01
                    • 2010-12-24
                    • 1970-01-01
                    • 2013-04-30
                    • 2013-01-19
                    • 1970-01-01
                    相关资源
                    最近更新 更多