web-dev-qa-db-ja.com

C ++関数getlineで2つ以上の区切り文字を使用できますか?

Getline関数で2つ以上の区切り文字を使用する方法を知りたいのですが、それが私の問題です:

プログラムはテキストファイルを読み取ります。各行は次のようになります。

   New Your, Paris, 100
   CityA, CityB, 200

私はgetl​​ine(file、line)を使用していますが、CityA、CityB、次に番号を取得したいときに、行全体を取得しました。そして、「、」区切り文字を使用すると、次の行がいつになるかわかりませんので、解決策を見つけようとしています。

ただし、コンマと\ nを区切り文字として使用するにはどうすればよいですか?ところで、私はcharではなくstring型を操作しているので、strtokは不可能です:/

いくつかの傷:

string line;
ifstream file("text.txt");
if(file.is_open())
   while(!file.eof()){
     getline(file, line);
        // here I need to get each string before comma and \n
   }
11
user6185425

std::getlineを使用して行を読み取ってから、その行を std::stringstream に渡し、そこからコンマ区切りの値を読み取ることができます。

string line;
ifstream file("text.txt");
if(file.is_open()){
   while(getline(file, line)){   // get a whole line
       std::stringstream ss(line);
        while(getline(ss, line, ',')){
             // You now have separate entites here
        }
   }
11
WhiZTiM

いいえ、_std::getline_()は単一の文字のみを受け入れ、デフォルトの区切り文字をオーバーライドします。 std::getline()には、複数の代替区切り文字のオプションがありません。

この種の入力を解析する正しい方法は、デフォルトの_std::getline_()を使用して行全体を_std::string_に読み取り、_std::istringstream_を構築してから、さらに解析して、コンマ区切り値。

ただし、コンマ区切り値を真に解析する場合は、 適切なCSVパーサー を使用する必要があります。

5
Sam Varshavchik

多くの場合、階層的なツリーのような方法で文字入力を解析する方がより直感的で効率的です。まず、文字列を主要なブロックに分割し、次に各ブロックを処理して、小さな部分に分割します。等々。

これに代わる方法は、strtokのようにトークン化することです。入力の最初から、入力の終わりに達するまで一度に1つのトークンを処理します。これは、実装が簡単なので、単純な入力を解析する場合に適しています。このスタイルは、ネストされた構造を持つ入力を解析するときにも使用できますが、これには何らかのコンテキスト情報を維持する必要があり、単一の関数またはコードの限られた領域内で維持するには複雑すぎます。

C++ stdライブラリに依存している人は通常、文字列入力をトークン化する_std::stringstream_とともに_std::getline_を使用することになります。しかし、これは区切り文字を1つだけ提供します。彼らはstrtokを使用することを決して考えません。なぜなら、それはCランタイムライブラリからの再入不可能なジャンクの断片だからです。そのため、最終的にはストリームを使用することになり、デリミタが1つだけの場合、階層解析スタイルを使用する必要があります。

しかし、zneakは_std::string::find_first_of_を呼び出しました。これは文字のセットを受け取り、そのセットの文字を含む文字列の先頭に最も近い位置を返します。そして、他のメンバー関数があります:_find_last_of_、_find_first_not_of_、その他、これらは文字列を解析するためだけに存在するようです。ただし、_std::string_は、有用なトークン化関数を提供するには至りません。

別のオプションは_<regex>_ライブラリで、これはあなたが望むものを何でもすることができますが、それは新しく、その構文に慣れる必要があります。

しかし、ごくわずかな労力で、_std::string_の既存の関数を活用して、ストリームに頼らずにトークン化タスクを実行できます。以下に簡単な例を示します。 get_to()はトークン化関数であり、tokenizeはその使用方法を示しています。

この例のコードは、解析される文字列の先頭から文字を絶えず消去し、部分文字列をコピーして返すため、strtokよりも遅くなります。これによりコードが理解しやすくなりますが、より効率的なトークン化が不可能になるわけではありません。これほど複雑なことはありません-現在の位置を追跡し、これを_std::string_メンバー関数のstart引数として使用し、ソース文字列を変更しないでください。 。そして、さらに優れたテクニックが存在することは間違いありません。

例のコードを理解するには、一番下から始めます。ここで、main()はどこにあり、関数の使用方法を確認できます。このコードの上部は、基本的なユーティリティ関数と愚かなコメントで占められています。

_#include <iostream>
#include <string>
#include <utility>

namespace string_parsing {
// in-place trim whitespace off ends of a std::string
inline void trim(std::string &str) {
    auto space_is_it = [] (char c) {
        // A few asks:
        // * Suppress criticism WRT localization concerns
        // * Avoid jumping to conclusions! And seeing monsters everywhere! 
        //   Things like...ah! Believing "thoughts" that assumptions were made
        //   regarding character encoding.
        // * If an obvious, portable alternative exists within the C++ Standard Library,
        //   you will see it in 2.0, so no new defect tickets, please.
        // * Go ahead and ignore the rumor that using lambdas just to get 
        //   local function definitions is "cheap" or "dumb" or "ignorant."
        //   That's the latest round of FUD from...*mumble*.
        return c > '\0' && c <= ' '; 
    };

    for(auto rit = str.rbegin(); rit != str.rend(); ++rit) {
        if(!space_is_it(*rit)) {
            if(rit != str.rbegin()) {
                str.erase(&*rit - &*str.begin() + 1);
            }
            for(auto fit=str.begin(); fit != str.end(); ++fit) {
                if(!space_is_it(*fit)) {
                    if(fit != str.begin()) {
                        str.erase(str.begin(), fit);
                    }
                    return;
    }   }   }   }
    str.clear();
}

// get_to(string, <delimiter set> [, delimiter])
// The input+output argument "string" is searched for the first occurance of one 
// from a set of delimiters.  All characters to the left of, and the delimiter itself
// are deleted in-place, and the substring which was to the left of the delimiter is
// returned, with whitespace trimmed.
// <delimiter set> is forwarded to std::string::find_first_of, so its type may match
// whatever this function's overloads accept, but this is usually expressed
// as a string literal: ", \n" matches commas, spaces and linefeeds.
// The optional output argument "found_delimiter" receives the delimiter character just found.
template <typename D>
inline std::string get_to(std::string& str, D&& delimiters, char& found_delimiter) {
    const auto pos = str.find_first_of(std::forward<D>(delimiters));
    if(pos == std::string::npos) {
        // When none of the delimiters are present,
        // clear the string and return its last value.
        // This effectively makes the end of a string an
        // implied delimiter.
        // This behavior is convenient for parsers which
        // consume chunks of a string, looping until
        // the string is empty.
        // Without this feature, it would be possible to 
        // continue looping forever, when an iteration 
        // leaves the string unchanged, usually caused by
        // a syntax error in the source string.
        // So the implied end-of-string delimiter takes
        // away the caller's burden of anticipating and 
        // handling the range of possible errors.
        found_delimiter = '\0';
        std::string result;
        std::swap(result, str);
        trim(result);
        return result;
    }
    found_delimiter = str[pos];
    auto left = str.substr(0, pos);
    trim(left);
    str.erase(0, pos + 1);
    return left;
}

template <typename D>
inline std::string get_to(std::string& str, D&& delimiters) {
    char discarded_delimiter;
    return get_to(str, std::forward<D>(delimiters), discarded_delimiter);
}

inline std::string pad_right(const std::string&     str,
                             std::string::size_type min_length,
                             char                   pad_char=' ')
{
    if(str.length() >= min_length ) return str;
    return str + std::string(min_length - str.length(), pad_char);
}

inline void tokenize(std::string source) {
    std::cout << source << "\n\n";
    bool quote_opened = false;
    while(!source.empty()) {
        // If we just encountered an open-quote, only include the quote character
        // in the delimiter set, so that a quoted token may contain any of the
        // other delimiters.
        const char* delimiter_set = quote_opened ? "'" : ",'{}";
        char delimiter;
        auto token = get_to(source, delimiter_set, delimiter);
        quote_opened = delimiter == '\'' && !quote_opened;
        std::cout << "    " << pad_right('[' + token + ']', 16) 
            << "   " << delimiter << '\n';
    }
    std::cout << '\n';
}
}

int main() {
    string_parsing::tokenize("{1.5, null, 88, 'hi, {there}!'}");
}
_

この出力:

_{1.5, null, 88, 'hi, {there}!'}

    []                 {
    [1.5]              ,
    [null]             ,
    [88]               ,
    []                 '
    [hi, {there}!]     '
    []                 }
_
2

私はそれがあなたが問題を攻撃するべきであるとは思わない(たとえそれができたとしても)。代わりに:

  1. 各行で読む必要があるものを使用します
  2. 次に、その行をコンマで分割して、必要な部分を取得します。

strtokが#2の仕事をする場合、文字列をいつでもchar配列に変換できます。

1
Scott Hunter