我需要解析一个看起来像这样的C ++ stdin输入:
N M(对)
0 0
2 1 (0,1)
2 0
5 8 (0,1) (1,3) (2,3) (0,2) (0,1) (2,3) (2,4) (2,4)
如果N> 0&& M> 0,然后是M对。这是一个单行输入,所以我不知道该怎么做。
我有一些解决方案,但有些东西告诉我这不是最好的解决方案。
void input(){
int a[100][2];
int n,m;
char ch;
cin >> n >> m;
for ( int i = 0; i < m; i++) {
cin >> ch >> a[i][0]>> ch>> a[i][1]>>ch;
}
cout << n << " " << m << " \n";
for ( int i=0; i < m; i++ ) {
cout << "(" << a[i][0] << " ," << a[i][1] << ")";
}
}
我的问题是,最好/更正确的方法是什么?
答案 0 :(得分:6)
由于输入数据到应用程序永远不可信任,因此添加错误检查以查看提供的数据确实有效(否则应用程序的结果在解析时可能会出错)非常重要。
处理此类错误的“C ++方式”是在负责解析数据的函数出现问题时抛出异常。
此函数的调用者将在 try-catch-block 中包装调用以捕获可能出现的错误。
定义用于保存数据对的自己的类型将极大地提高代码的可读性,以下实现的输出和本文后面的内容是相同的。
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
struct Pair {
Pair (int a, int b)
: value1 (a), value2 (b)
{}
static Pair read_from (std::istream& s) {
int value1, value2;
if ((s >> std::ws).peek () != '(' || !s.ignore () || !(s >> value1))
throw std::runtime_error ("unexpected tokens; expected -> (, <value1>");
if ((s >> std::ws).peek () != ',' || !s.ignore () || !(s >> value2))
throw std::runtime_error ("unexpected tokens; expected -> , <value2>");
if ((s >> std::ws).peek () != ')' || !s.ignore ())
throw std::runtime_error ("unexpected token;expected -> )");
return Pair (value1,value2);
}
int value1, value2;
};
我注意到的一件事,程序员可能很难掌握上述内容,s >> std::ws
;它用于消耗可用的空格,以便我们可以使用.peek
来获取下一个非空格字符。
我实现静态函数read_from
而不是ostream& operator>>(ostream&, Pair&)
的原因是后者要求我们在读取流之前创建一个对象,这在某些情况下是不合需要的。功能
void
parse_data () {
std::string line;
while (std::getline (std::cin, line)) {
std::istringstream iss (line);
int N, M;
if (!(iss >> N >> M))
throw "unable to read N or M";
else
std::cerr << "N = " << N << ", M = " << M << "\n";
for (int i =0; i < M; ++i) {
Pair data = Pair::read_from (iss);
std::cerr << "\tvalue1 = " << data.value1 << ", ";
std::cerr << "\tvalue2 = " << data.value2 << "\n";
}
}
}
通常我不建议仅以大写形式命名非常量变量,但要更清楚地说明哪个变量包含的内容与输入描述的名称相同。
int
main (int argc, char *argv[])
{
try {
parse_data ();
} catch (std::exception& e) {
std::cerr << e.what () << "\n";
}
}
解析数据以及检查错误的直接方法是使用以下内容,但使用用户定义对象和运算符重载可以大大改善。
- 使用 std :: getline
读取每一行- 使用读取行
构造n std :: istringstream iss(line)- 尝试使用 iss&gt;&gt;读取两个整数N>&gt;中号
- 使用带有 iss&gt;&gt;的std :: string s1 *读取 M “words” S1;
醇>
- 使用 s1 作为初始化程序构建 std :: istringstream inner_iss
- 看看下一个可用的字符是
(
&amp;&amp;忽略这个字符- 读取整数
- 看看下一个可用的字符是
,
&amp;&amp;忽略这个字符- 读取整数
- 看看下一个可用的字符是
)
&amp;&amp;忽略这个字符
如果在第4步之后字符串流不为空,或者 iss.good()在步骤之间的任何地方返回false,则数据读取中出现语法错误。
可以通过以下链接找到源代码(代码放在别处以节省空间):
N = 0, M = 0
N = 2, M = 1
value1 = 0, value2 = 1
N = 2, M = 0
N = 5, M = 8
value1 = 0, value2 = 1
value1 = 1, value2 = 3
value1 = 2, value2 = 3
value1 = 0, value2 = 2
value1 = 0, value2 = 1
value1 = 2, value2 = 3
value1 = 2, value2 = 4
value1 = 2, value2 = 4
答案 1 :(得分:1)
如果要求操作的数据全部在一行上,那么最好的技术可能是将该行读入字符串,然后解析从输入字符串初始化的字符串流。
您应该考虑是否需要验证括号和逗号是否真的是括号和逗号 - 如果输入是:
,您是否会生成错误?23 2 @3;8= % 7 % 12 %
您的代码会认为此时此代码有效。
答案 2 :(得分:1)
这种类似的规范解决方案是为其定义类型
对,并为它实现一个>>
运算符。类似的东西:
class Pair
{
int first;
int second;
public:
Pair( int first, int second );
// ...
};
std::istream&
operator>>( std::istream& source, Pair& object )
{
char open;
char separ;
char close;
int first;
int second;
if ( source >> open >> first >> separ >> second >> close
&& open == '(' && separ == ',' && close == ')' ) {
object = Pair( first, second );
} else {
source.setstate( std::ios_base::failbit );
}
return source;
}
鉴于此,要阅读文件:
std::string line;
while ( std::getline( source, line ) ) {
std::istringstream l( line );
int n;
int m;
std::vector<Pair> pairs;
l >> n >> m;
if ( !l ) {
// Syntax error...
}
Pair p;
while ( l >> p ) {
pairs.push_back( p );
}
if ( ! l.eof() ) {
// Error encountered somewhere...
}
// Other consistency checks...
}
答案 3 :(得分:1)
我更喜欢Boost.Spirit来执行此类任务:
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <string>
#include <iostream>
struct input {
int x, y;
typedef std::pair<int, int> pair;
std::vector< pair > pairs;
};
BOOST_FUSION_ADAPT_STRUCT(
input,
(int, x)
(int, y)
(std::vector< input::pair >, pairs))
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
template<typename Iterator>
struct input_parser : qi::grammar<Iterator, input(), ascii::space_type> {
input_parser() : input_parser::base_type(start) {
// two integers followed by a possibly empty list of pairs
start = qi::int_ >> qi::int_ >> *pair;
// a tuple delimited by braces and values separated by comma
pair = '(' >> qi::int_ >> ',' >> qi::int_ >> ')';
}
qi::rule<Iterator, input(), ascii::space_type> start;
qi::rule<Iterator, input::pair(), ascii::space_type> pair;
};
template<typename Iterator>
void parse_and_print(Iterator begin, Iterator end) {
input x;
input_parser<Iterator> p;
bool r = qi::phrase_parse(begin, end, p, ascii::space, x);
if(!r) {
std::cerr << "Error parsing" << std::endl;
return;
}
std::cout << "Output" << std::endl;
std::cout << "x: " << x.x << std::endl;
std::cout << "y: " << x.y << std::endl;
if(x.pairs.empty()) {
std::cout << "No pairs.";
} else {
for(std::vector<input::pair>::iterator it = x.pairs.begin();
it != x.pairs.end(); ++it) {
std::cout << "(" << it->first << ',' << it->second << ") ";
}
}
std::cout << std::endl;
}
int main()
{
namespace qi = boost::spirit::qi;
std::string input1 = "0 0";
std::string input2 = "2 1 (0,1)";
std::string input3 = "2 0";
std::string input4 = "5 8 (0,1) (1,3) (2,3) (0,2) (0,1) (2,3) (2,4) (2,4)";
parse_and_print(input1.begin(), input1.end());
parse_and_print(input2.begin(), input2.end());
parse_and_print(input3.begin(), input3.end());
parse_and_print(input4.begin(), input4.end());
return 0;
}
答案 4 :(得分:-1)
由于你已经注意到输入中的模式,所以像字符串标记符这样的东西可以解决你的问题。
为此您可以使用strtok
功能。
同样适用于Boost library的实施也很有用,并且很好地举例说明here