【问题标题】:How do I parse this file in cpp?如何在 cpp 中解析此文件?
【发布时间】:2020-02-16 06:19:19
【问题描述】:

我要解析一个内容如下的文件:

2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13

这里,2是行数,300是权重。

每一行都有一个字符串和一个数字(价格)。与 3 和 400 相同。

我需要将 130、456 存储在一个数组中。

目前,我正在读取文件,每一行都被处理为std::string。我需要帮助才能更进一步。

代码:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

//void processString(string line);
void process2(string line);

int main(int argc, char ** argv) {
    cout << "You have entered " << argc <<
        " arguments:" << "\n";

    for (int i = 1; i < argc; ++i)
        cout << argv[i] << "\n";

    //2, 4 are the file names

    //Reading file - market price file
    string line;
    ifstream myfile(argv[2]);
    if (myfile.is_open()) {
        while (getline(myfile, line)) {
            //  cout << line << '\n';
        }
        myfile.close();
    } else cout << "Unable to open market price file";

    //Reading file - price list file
    string line_;
    ifstream myfile2(argv[4]);
    int c = 1;
    if (myfile2.is_open()) {
        while (getline(myfile2, line_)) {
            // processString(line_);
            process2(line_);
        }
        myfile2.close();
    } else cout << "Unable to open price lists file";

    //processString(line_);
    return 0;
}

void process2(string line) {

    string word = "";

    for (auto x: line) {
        if (x == ' ') {
            word += " ";
        } else {
            word = word + x;
        }
    }
    cout << word << endl;
}

有没有像 Java 那样的拆分功能,所以我可以将所有内容拆分并存储为令牌?

【问题讨论】:

标签: c++ file parsing


【解决方案1】:

您的帖子中有 2 个问题:

  1. 如何在 cpp 中解析这个文件?
  2. 是否有像 Java 那样的拆分功能,所以我可以将所有内容拆分并存储为令牌?

我将回答这两个问题并展示一个演示示例。

让我们从将字符串拆分为标记开始。有几种可能性。我们从简单的开始。

由于字符串中的标记由空格分隔,我们可以利用提取运算符 (>>) 的功能。这将从输入流中读取数据,直到空格,然后将此读取的数据转换为指定的变量。你知道这个操作是可以链式的。

那么对于示例字符串

    const std::string line{ "Token1 Token2 Token3 Token4" };

您可以简单地将其放入std::istringstream,然后从流中提取变量:

    std::istringstream iss1(line);
    iss1 >> subString1 >> subString2 >> subString3 >> subString4;

缺点是需要写很多东西,而且要知道字符串的元素个数。

我们可以通过使用向量作为taget 数据存储并使用其范围构造函数填充它来克服这个问题。向量范围构造函数采用 begin 和 end 插入器并将数据复制到其中。

作为迭代器,我们使用std::istream_iterator。简单来说,这将调用提取器运算符 (>>),直到所有数据都被消耗完。无论我们将拥有多少数据。

这将如下所示:

    std::istringstream iss2(line);
    std::vector token(std::istream_iterator<std::string>(iss2), {});

这可能看起来很复杂,但其实不然。我们定义了一个std::vector 类型的变量“token”。我们使用它的范围构造函数。

而且,我们可以在没有模板参数的情况下定义 std::vector。编译器可以从给定的函数参数中推断出参数。此功能称为 CTAD(“类模板参数推导”,需要 C++17)。

此外,您可以看到我没有明确使用“end()”-iterator。

这个迭代器将从带有正确类型的空大括号括起来的默认初始值设定项构造,因为由于 std::vector 构造函数需要,它将被推断为与第一个参数的类型相同。


还有一个额外的解决方案。这是最强大的解决方案,因此一开始可能会有点复杂。

这样可以避免使用 std::istringstream 并使用 std::sregex_token_iterator 直接将字符串转换为令牌。使用非常简单。结果是用于拆分原始字符串的单行:

std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

因此,现代 C++ 有一个内置功能,该功能正是为标记字符串而设计的。它被称为std::sregex_token_iterator。这是什么东西?

顾名思义,它是一个迭代器。它将遍历一个字符串(因此它的名称中包含“s”)并返回拆分的标记。标记将再次匹配正则表达式。或者,在本地,分隔符将被匹配,其余的将被视为令牌并返回。这将通过其构造函数中的最后一个标志来控制。

让我们看看这个构造函数:

token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

第一个参数是它应该在源字符串中开始的位置,第二个参数是结束位置,迭代器应该工作到该位置。最后一个参数是:

  • 1,如果您想对正则表达式进行肯定匹配
  • -1,将返回与正则表达式不匹配的所有内容

最后但并非最不重要的是正则表达式本身。请阅读网络 abot 正则表达式。有大量可用的页面。

请在此处查看所有 3 个解决方案的演示:

#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <sstream>
#include <iterator>
#include <algorithm>

/// Split string into tokens
int main() {

    // White space separated tokens in a string
    const std::string line{ "Token1 Token2 Token3 Token4" };

    // Solution 1: Use extractor operator ----------------------------------

    // Here, we will store the result
    std::string subString1{}, subString2{}, subString3{}, subString4{};

    // Put the line into an istringstream for easier extraction
    std::istringstream iss1(line);
    iss1 >> subString1 >> subString2 >> subString3 >> subString4;

    // Show result
    std::cout << "\nSolution 1:  Use inserter operator\n- Data: -\n" << subString1 << "\n"
        << subString2 << "\n" << subString3 << "\n" << subString4 << "\n";


    // Solution 2: Use istream_iterator ----------------------------------
    std::istringstream iss2(line);
    std::vector token(std::istream_iterator<std::string>(iss2), {});

    // Show result
    std::cout << "\nSolution 2:  Use istream_iterator\n- Data: -\n";
    std::copy(token.begin(), token.end(), std::ostream_iterator<std::string>(std::cout, "\n"));


    // Solution 3: Use std::sregex_token_iterator ----------------------------------
    const std::regex re(" ");

    std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

    // Show result
    std::cout << "\nSolution 3:  Use sregex_token_iterator\n- Data: -\n";
    std::copy(token2.begin(), token2.end(), std::ostream_iterator<std::string>(std::cout, "\n"));


    return 0;
}


所以,现在是关于如何阅读文本文件的答案。

创建正确的数据结构至关重要。然后,覆盖插入器和提取器操作符,将上述功能放入其中。

请看下面的演示示例。当然还有很多其他可能的解决方案:

#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>

struct ItemAndPrice {
    // Data
    std::string item{};
    unsigned int price{};

    // Extractor
    friend std::istream& operator >> (std::istream& is, ItemAndPrice& iap) {

        // Read a complete line from the stream and check, if that worked
        if (std::string line{}; std::getline(is, line)) {

            // Read the item and price from that line and check, if that worked
            if (std::istringstream iss(line); !(iss >> iap.item >> iap.price))

                // There was an error, while reading item and price. Set failbit of input stream
                is.setf(std::ios::failbit);
        }
        return is;
    }

    // Inserter
    friend std::ostream& operator << (std::ostream& os, const ItemAndPrice& iap) {
        // Simple output of our internal data
        return os << iap.item << " " << iap.price;
    }
};

struct MarketPrice {
    // Data
    std::vector<ItemAndPrice> marketPriceData{};
    size_t numberOfElements() const { return marketPriceData.size(); }
    unsigned int weight{};

    // Extractor
    friend std::istream& operator >> (std::istream& is, MarketPrice& mp) {

        // Read a complete line from the stream and check, if that worked
        if (std::string line{}; std::getline(is, line)) {

            size_t numberOfEntries{};
            // Read the number of following entries and the weigth from that line and check, if that worked
            if (std::istringstream iss(line); (iss >> numberOfEntries >> mp.weight)) {

                mp.marketPriceData.clear();
                // Now copy the numberOfEntries next lines into our vector
                std::copy_n(std::istream_iterator<ItemAndPrice>(is), numberOfEntries, std::back_inserter(mp.marketPriceData));
            }
            else {
                // There was an error, while reading number of following entries and the weigth. Set failbit of input stream
                is.setf(std::ios::failbit);
            }
        }
        return is;
    };

    // Inserter
    friend std::ostream& operator << (std::ostream& os, const MarketPrice& mp) {

        // Simple output of our internal data
        os << "\nNumber of Elements: " << mp.numberOfElements() << "   Weight: " << mp.weight << "\n";

        // Now copy all marekt price data to output stream
        if (os) std::copy(mp.marketPriceData.begin(), mp.marketPriceData.end(), std::ostream_iterator<ItemAndPrice>(os, "\n"));

        return os;
    }
};

// For this example I do not use argv and argc and file streams. 
// This, because on Stackoverflow, I do not have files on Stackoverflow
// So, I put the file data in an istringstream. But for the below example, 
// there is no difference between a file stream or a string stream

std::istringstream sourceFile{R"(2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13)"};


int main() {

    // Here we will store all the resulting data
    // So, read the complete source file, parse the data and store result in vector
    std::vector mp(std::istream_iterator<MarketPrice>(sourceFile), {});

    // Now, all data are in mp. You may work with that now

    // Show result on display
    std::copy(mp.begin(), mp.end(), std::ostream_iterator<MarketPrice>(std::cout, "\n"));

    return 0;
}

【讨论】:

  • 这太彻底了,非常感谢。我使用传统方法使用 isstream 然后循环实现了该问题的解决方案,但我将尝试一下。这似乎是一个非常优化的问题解决方案......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-23
  • 1970-01-01
  • 2020-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多