1

次のような C++ stdin 入力を解析する必要があります。

NM (ペア)

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 個のペアが続きます。1行入力なのでやり方がわかりません。

私にはいくつかの解決策がありますが、それが最善の解決策ではないことがわかります。

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] << ")";   
    }
}

私の質問は、これを行うための最良/より正しい方法は何ですか?

4

5 に答える 5

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;
};

プログラマーが上記について理解するのが難しいかもしれないことに気付いた 1 つのことは、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";
    }
  }
}

通常、非 const 変数に大文字のみで名前を付けることはお勧めしませんが、どの変数に入力の説明と同じ名前を使用するかをより明確にするために使用します。

int
main (int argc, char *argv[])
{
  try {
    parse_data ();

  } catch (std::exception& e) {
    std::cerr << e.what () << "\n";
  }
}

ユーザー定義型を使用しない場合

データを解析し、エラーをチェックする簡単な方法は、次のようなものを使用することですが、ユーザー定義オブジェクトと演算子のオーバーロードを使用すると大幅に改善される可能性があります。

  1. std::getlineを使用して各行を読み取る
  2. 読み取り行でn std::istringstream iss (行)を構築します
  3. iss >> N >> Mを使用して 2 つの int を読み取ろうとする
  4. std::string s1* とiss >> s1を使用して、 M個の 「単語」を読み取ります。
    1. s1を初期化子として使用してstd::istringstream inner_issを構築します
    2. 利用可能な次の文字が(&& この文字を無視することを確認してください
    3. 整数を読み取る
    4. 利用可能な次の文字が,&& この文字を無視することを確認してください
    5. 整数を読み取る
    6. 利用可能な次の文字が)&& この文字を無視することを確認してください

ステップ 4 の後で stringstream が空でない場合、または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
于 2012-07-18T12:38:38.433 に答える
1

操作のデータがすべて 1 行にあることが要件である場合、最適な手法はおそらく、その行を文字列に読み込んでから、入力文字列から初期化された文字列ストリームを解析することです。

かっことコンマが本当にかっことコンマであることを確認する必要があるかどうかを検討する必要があります。入力が次の場合にエラーが発生しますか?

23 2 @3;8= %      7      %     12     %

あなたのコードは、現時点でそれを有効なものとして受け入れます。

于 2012-07-18T12:40:17.293 に答える
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...
}
于 2012-07-18T12:44:07.827 に答える
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;
}
于 2012-07-18T13:01:26.863 に答える
-1

入力のパターンに気付いたので、文字列トークナイザーのようなものは問題を解決します。

そのためには、strtok 関数を使用できます。また、Boostライブラリの実装は便利で、ここでよく例示されています

于 2012-07-26T07:50:28.410 に答える