如何在C++中有效地提取字符串模式
How to extract the string pattern in C++ efficiently?
我有一个格式如下的模式:
AUTHOR, "TITLE" (PAGES pp.) [CODE STATUS]
例如,我有一个字符串
P.G. Wodehouse, "Heavy Weather" (336 pp.) [PH.409 AVAILABLE FOR LENDING]
我想提取
AUTHOR = P.G. Wodehouse
TITLE = Heavy Weather
PAGES = 336
CODE = PH.409
STATUS = AVAILABLE FOR LENDING
我只知道如何在Python中做到这一点,然而,在C++中有什么有效的方法可以做到同样的事情吗?
与Python中的方法完全相同。C++11有正则表达式(对于早期的C++,有Boost正则表达式。)至于读取循环:
std::string line;
while ( std::getline( file, line ) ) {
// ...
}
几乎与完全相同
for line in file:
# ...
唯一的区别是:
C++版本不会将尾随的
'n'
放入缓冲区中。(一般来说,C++版本在线路末端处理方面可能不太灵活。)如果出现读取错误,C++版本将中断循环;Python版本将引发一个异常。
在你的情况下,两者都不应该成为问题。
编辑:
我突然想到,虽然C++和Python中的正则表达式非常相似,但使用它们的语法并不完全相同。因此:
在C++中,您通常会在使用正则表达式之前声明它的一个实例;像Python的re.match( r'...', line )
这样的东西在理论上是可能的,但不是很习惯(它仍然需要在表达式中显式地构造一个正则表达式对象)。此外,match函数只是返回一个布尔值;如果您想要捕获,您需要为它们定义一个单独的对象。典型的用途可能是:
static std::regex const matcher( "the regular expression" );
std::smatch forCaptures;
if ( std::regex_match( line, forCaptures, matcher ) ) {
std::string firstCapture = forCaptures[1];
// ...
}
这对应于Python:
m = re.match( 'the regular expression', line )
if m:
firstCapture = m.group(1)
# ...
编辑:
另一个答案建议operator>>
过载;我完全同意。只是出于好奇,我试了一下;下面这样的方法效果很好:
struct Book
{
std::string author;
std::string title;
int pages;
std::string code;
std::string status;
};
std::istream&
operator>>( std::istream& source, Book& dest )
{
std::string line;
std::getline( source, line );
if ( source )
{
static std::regex const matcher(
R"^(([^,]*),s*"([^"]*)"s*((d+) pp.)s*[(S+)s*([^]]*)])^"
);
std::smatch capture;
if ( ! std::regex_match( line, capture, matcher ) ) {
source.setstate( std::ios_base::failbit );
} else {
dest.author = capture[1];
dest.title = capture[2];
dest.pages = std::stoi( capture[3] );
dest.code = capture[4];
dest.status = capture[5];
}
}
return source;
}
一旦你做到了这一点,你就可以写这样的东西:
std::vector<Book> v( (std::istream_iterator<Book>( inputFile )),
(std::istream_iterator<Book>()) );
并在初始化一个向量时加载整个文件。
注意operator>>
中的错误处理。如果一行格式错误,我们设置failbit
;这是C++中的标准约定。
编辑:
既然有这么多讨论:以上内容适用于小型一次性程序,比如学校项目,或者一次性程序,这些程序会读取当前文件,以新格式输出,然后被丢弃。在生产代码中,我会坚持支持注释和空行;在出现错误的情况下继续,以便报告多个错误(使用行号),并可能报告连续行(因为标题可能会变得足够长,以至于难以处理)。如果除了需要输出行号之外没有其他原因,那么用operator>>
这样做是不实际的,所以我会沿着以下行使用解析器:
int
getContinuationLines( std::istream& source, std::string& line )
{
int results = 0;
while ( source.peek() == '&' ) {
std::string more;
std::getline( source, more ); // Cannot fail, because of peek
more[0] = ' ';
line += more;
++ results;
}
return results;
}
void
trimComment( std::string& line )
{
char quoted = ' ';
std::string::iterator position = line.begin();
while ( position != line.end() && (quoted != ' ' || *position == '#') ) {
if ( *position == '' && std::next( position ) != line.end() ) {
++ position;
} else if ( *position == quoted ) {
quoted = ' ';
} else if ( *position == '"' || *position == ''' ) {
quoted = *position;
}
++ position;
}
line.erase( position, line.end() );
}
bool
isEmpty( std::string const& line )
{
return std::all_of(
line.begin(),
line.end(),
[]( unsigned char ch ) { return isspace( ch ); } );
}
std::vector<Book>
parseFile( std::istream& source )
{
std::vector<Book> results;
int lineNumber = 0;
std::string line;
bool errorSeen = false;
while ( std::getline( source, line ) ) {
++ lineNumber;
int extraLines = getContinuationLines( source, line );
trimComment( line );
if ( ! isEmpty( line ) ) {
static std::regex const matcher(
R"^(([^,]*),s*"([^"]*)"s*((d+) pp.)s*[(S+)s*([^]]*)])^"
);
std::smatch capture;
if ( ! std::regex_match( line, capture, matcher ) ) {
std::cerr << "Format error, line " << lineNumber << std::endl;
errorSeen = true;
} else {
results.emplace_back(
capture[1],
capture[2],
std::stoi( capture[3] ),
capture[4],
capture[5] );
}
}
lineNumber += extraLines;
}
if ( errorSeen ) {
results.clear(); // Or more likely, throw some sort of exception.
}
return results;
}
这里真正的问题是如何向调用者报告错误;我怀疑在大多数情况下,例外是合适的,但根据用例的不同,其他替代方案也可能有效。在这个例子中,我只是返回一个空向量。(注释和续行之间的交互可能也需要更好地定义,并根据其定义方式进行修改。)
您的输入字符串分隔良好,因此我建议在regex
上使用提取运算符,以提高速度并便于使用。
你首先需要为你的书创建一个struct
:
struct book{
string author;
string title;
int pages;
string code;
string status;
};
然后你需要编写实际的提取运算符:
istream& operator>>(istream& lhs, book& rhs){
lhs >> ws;
getline(lhs, rhs.author, ',');
lhs.ignore(numeric_limits<streamsize>::max(), '"');
getline(lhs, rhs.title, '"');
lhs.ignore(numeric_limits<streamsize>::max(), '(');
lhs >> rhs.pages;
lhs.ignore(numeric_limits<streamsize>::max(), '[');
lhs >> rhs.code >> ws;
getline(lhs, rhs.status, ']');
return lhs;
}
这会给你巨大的力量。例如,您可以将istream
中的所有书籍提取到vector
中,如下所示:
istringstream foo("P.G. Wodehouse, "Heavy Weather" (336 pp.) [PH.409 AVAILABLE FOR LENDING]nJohn Bunyan, "The Pilgrim's Progress" (336 pp.) [E.1173 CHECKED OUT]");
vector<book> bar{ istream_iterator<book>(foo), istream_iterator<book>() };
使用flex(它生成C或C++代码,用作部分或完整程序)
%%
^[^,]+/, {printf("Autor: %sn",yytext );}
"[^"]+" {printf("Title: %sn",yytext );}
([^ ]+/[ ]pp. {printf("Pages: %sn",yytext+1);}
..................
.|n {}
%%
(未经测试)
以下是代码:
#include <iostream>
#include <cstring>
using namespace std;
string extract (string a)
{
string str = "AUTHOR = "; //the result string
int i = 0;
while (a[i] != ',')
str += a[i++];
while (a[i++] != '"');
str += "nTITLE = ";
while (a[i] != '"')
str += a[i++];
while (a[i++] != '(');
str += "nPAGES = ";
while (a[i] != ' ')
str += a[i++];
while (a[i++] != '[');
str += "nCODE = ";
while (a[i] != ' ')
str += a[i++];
while (a[i++] == ' ');
str += "nSTATUS = ";
while (a[i] != ']')
str += a[i++];
return str;
}
int main ()
{
string a;
getline (cin, a);
cout << extract (a) << endl;
return 0;
}
快乐编码:)
- 清除输入缓冲区后未提取字符串流
- 提取字符串中的随机单词
- 提取C++字符串
- 如何提取字符串中的特定值
- 如何提取字符串中的整数?
- 正则表达式:从模式字符串 1.string2'string3 中提取字符串,其中字符串 1 可以包含 '." 字符
- C - 从逗号分隔的浮点线提取字符串
- 如何使用正则表达式提取字符串的不匹配部分
- 处理从 std::istream 中提取字符串的空字符串大小写
- 可视化从文本C++中提取字符串数据
- 如何在C++中有效地提取字符串模式
- 分析文本文件以提取字符串
- C++:从两个符号之间提取字符串
- 提取字符串的一部分并将部分放入字符数组和另一个字符串变量C++
- 如何在c++中提取字符串的一部分
- 使用getline(),然后从用户输入中提取字符串(使用c++)
- 尝试从文本文件中读取列表,然后搜索文本文件,并在c++中提取字符串
- 试图从c++文本文件的单行中提取字符串的一部分
- 从字符数组中提取字符串
- 从JavaScript代码中提取字符串