我正在编写一个软件,它需要我处理从带有 libcurl 的网页获取的数据。当我得到数据时,由于某种原因,它有额外的换行符。我需要想办法只允许字母、数字和空格。并删除其他所有内容,包括换行符。有什么简单的方法可以做到这一点?谢谢。
12 回答
如果要删除该字符或要保留该字符,请编写一个接受 achar
并返回的函数: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
函数,则需要进行强制转换以消除std::isalnum
C 标准库标头中的函数<cctype>
(这是您要使用的)和std::isalnum
C++ 标准库标头中的<locale>
函数(不是你想使用,除非你想执行特定于语言环境的字符串处理):
s.erase(std::remove_if(s.begin(), s.end(), (int(*)(int))std::isalnum), s.end());
这同样适用于任何序列容器(包括和std::string
)。这个成语通常被称为“擦除/删除”成语。该算法也适用于普通数组。仅对序列进行一次传递,因此它具有线性时间复杂度 。std::vector
std::deque
std::remove_if
std::remove_if
以前的使用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());
erase
如果您使用的是string
.
#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);
只是多扩展一点 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 字符。
对不同方法进行基准测试。
如果您正在寻找我制定的基准。
(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;
}
我的解决方案
对于按位方法,您可以直接在我的 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);
remove_copy_if标准算法非常适合您的情况。
#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
将 NOTs 返回值的函数传递给not1
您,只留下您想要的字母数字内容。
您可以通过这种方式使用 remove-erase 算法 -
// Removes all punctuation
s.erase( std::remove_if(s.begin(), s.end(), &ispunct), s.end());
下面的代码对于给定的字符串应该可以正常工作s
。它正在使用<algorithm>
和<locale>
库。
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());
提到的解决方案
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());
以下对我有用。
str.erase(std::remove_if(str.begin(), str.end(), &ispunct), str.end());
str.erase(std::remove_if(str.begin(), str.end(), &isspace), str.end());
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;
}