我的格式如下:
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 ++中做同样的事情?
答案 0 :(得分:7)
与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 = '\0';
std::string::iterator position = line.begin();
while ( position != line.end() && (quoted != '\0' || *position == '#') ) {
if ( *position == '\' && std::next( position ) != line.end() ) {
++ position;
} else if ( *position == quoted ) {
quoted = '\0';
} 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;
}
这里真正的问题是你如何向调用者报告错误;我怀疑在大多数情况下,异常是合适的,但根据用例,其他替代方案也可能有效。在这个例子中,我只返回一个空向量。 (注释和延续线之间的相互作用可能也需要更好地定义,并根据它的定义进行修改。)
答案 1 :(得分:3)
您的输入字符串分隔很好,因此为了速度和易用性,我建议在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>() };
答案 2 :(得分:2)
使用flex(它生成C或C ++代码,用作部分或完整程序)
%%
^[^,]+/, {printf("Autor: %s\n",yytext );}
\"[^"]+\" {printf("Title: %s\n",yytext );}
\([^ ]+/[ ]pp\. {printf("Pages: %s\n",yytext+1);}
..................
.|\n {}
%%
(未测试的)
答案 3 :(得分:0)
以下是代码:
#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;
}
快乐编码:)