【问题标题】:How to read a CSV list from a .txt file and seperate each of the items into their own array如何从 .t​​xt 文件中读取 CSV 列表并将每个项目分隔到它们自己的数组中
【发布时间】:2020-06-21 21:24:54
【问题描述】:

这是我正在使用的列表:

绘画,150.10,10 灯,100.20,10 地毯,200.00,10 时钟,100.00,10 雕塑,300.00,10 照片,200.00,10 陶器,100.00,10 观看,300.00,10 韦奇伍德,70.00,10 瓷砖,500.00,10

到目前为止,我已经想出了这个来分隔项目并遇到错误:

ifstream file("antiquelist.txt");
string name;
float price;
int quantity;
while(file.good()){
    getline(file, name, ',', price, ',', quantity);
    cout << name << endl;

【问题讨论】:

标签: c++ arrays csv getline


【解决方案1】:

有多种方法可以将行中的值解析为您想要的三个值std::string itemdouble priceint qty。您可以采取多种路线,(1.) 直接从文件流中读取,或 (2.) 从每一行创建一个 std::stringstream 并从该行读取值。不同之处在于如果直接从文件流中读取,则需要删除输入流中留下的'\n'

到目前为止,最强大的方法是创建一个字符串流,因此如果您尝试直接从文件流中解析,那么输入流中剩余的内容不取决于从文件流中读取和忽略的内容。

基本方法是声明一个字符串来保存每一行并打开文件(并验证文件是否已打开以供阅读),例如

    std::string s;
    std::ifstream f (argv[1]);  /* open file stream */

    if (!f.good()) { /* validate file stream state good */
        std::cerr << "error: file open failed.\n";
        return 1;
    }

下一个循环,读取每一行并从每一行创建一个字符串流。如何从字符串流中解析值可以采用几种不同的方法,但只需一次验证每个值的读取,避免读取到临时字符串然后转换为 doubleint,这需要单独的 try {...} catch() {..}异常处理。

基本方法是使用带有',' 分隔符的getline (...,',') 读取项目,这将读取直到第一个',' 的所有内容,而丢弃','。然后您可以直接阅读您的price。字符提取读取价格将停止在',',您可以使用getline (...,',') 将其读取到临时变量中(您可以只读取char,但如果price 的末尾和','。最后,直接读取qty即可。(验证每次读取是否成功)

    while (getline (f, s)) {        /* read each line into s */
        int qty;
        double price;
        std::string item, tmp;
        std::stringstream ss (s);   /* create stringstream from s */
        /* read all 3 values from stringstream into separate values */
        if (getline(ss,item,',') && ss >> price && getline(ss,tmp,',') && ss >> qty) {
            std::cout << "item: " << std::left << std::setw(10) << item 
                        << "  price: " << std::right << std::setw(5) 
                        << std::fixed << std::setprecision(1) << price 
                        << "  qty: " << qty << '\n';
        }
        else    /* if 3 values not read/handle error */
            std::cerr << "invalid format: '" << s << "'\n";
    }

完全放在一个例子中:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>

int main (int argc, char **argv) {

    if (argc < 2) { /* validate at least 1 argument for filename */
        std::cerr << "usage: " << argv[0] << " filename\n";
        return 1;
    }
    std::string s;
    std::ifstream f (argv[1]);  /* open file stream */

    if (!f.good()) { /* validate file stream state good */
        std::cerr << "error: file open failed.\n";
        return 1;
    }

    while (getline (f, s)) {        /* read each line into s */
        int qty;
        double price;
        std::string item, tmp;
        std::stringstream ss (s);   /* create stringstream from s */
        /* read all 3 values from stringstream into separate values */
        if (getline(ss,item,',') && ss >> price && getline(ss,tmp,',') && ss >> qty) {
            std::cout << "item: " << std::left << std::setw(10) << item 
                        << "  price: " << std::right << std::setw(5) 
                        << std::fixed << std::setprecision(1) << price 
                        << "  qty: " << qty << '\n';
        }
        else    /* if 3 values not read/handle error */
            std::cerr << "invalid format: '" << s << "'\n";
    }
}

使用/输出示例

$ ./bin/readartitems dat/antiquelist.txt
item: Painting    price: 150.1  qty: 10
item: Lamp        price: 100.2  qty: 10
item: Rug         price: 200.0  qty: 10
item: Clock       price: 100.0  qty: 10
item: Sculpture   price: 300.0  qty: 10
item: Photograph  price: 200.0  qty: 10
item: Pottery     price: 100.0  qty: 10
item: Watch       price: 300.0  qty: 10
item: Wedgwood    price:  70.0  qty: 10
item: Tiles       price: 500.0  qty: 10

检查一下,如果您还有其他问题,请告诉我。

【讨论】:

    【解决方案2】:

    没有人会对以下内容感兴趣,因为这个答案是在提出问题 5 天后给出的。

    此外,OP 是 C++ 的新手,wnd 不会理解它。但由于这个问题是用 C++ 标记的,而其他答案都非常 C 风格,我想在这里展示一个更现代的 C++ 解决方案,它采用面向对象的方法。

    而且,我到处都看到滥用std::getline 来分割字符串,这令人难以置信,因为存在用于分割字符串的专用函数。 std::getline 的预期用途是从流中“获取一行”。顾名思义。人们摆弄这个函数并搜索分隔符等,但在我看来,我们不应该这样做。

    大约 10 年来,我们有一个专门的、特殊的 C++ 功能,用于将字符串拆分为标记,专门为此目的而设计。 std::sregex_token_iterator。而且因为我们有这样一个专用功能,所以我们应该简单地使用它。

    它背后的想法是迭代器的概念。在 C++ 中,我们有许多容器并且总是迭代器,以迭代这些容器中的相似元素。而一个字符串,具有相似的元素(标记),用分隔符分隔,也可以看作是这样的容器。并且使用std::sregex:token_iterator,我们可以遍历字符串的元素/令牌/子字符串,有效地将其拆分。

    这个迭代器非常强大,你可以用它做更多花哨的事情。但这对这里来说太过分了。重要的是,将字符串拆分为标记是一行。例如,使用范围构造函数来迭代标记的变量定义。例如:

        // The delimiter
        const std::regex delimiter{ "," };
    
        // Test data
        std::string csvData("d1,d2,d3,d4,d5");
    
        // Split the string
        std::vector<std::string> tokens(std::sregex_token_iterator(csvData.begin(), csvData.end(), delimiter, -1), {});
    

    Ant 因为是一个迭代器,所以你可以将它与许多算法一起使用。你真的应该stduy并使用它。对于所有那些非常关心效率的人,请注意,在 90% 的情况下,只会解析几行。没问题。

    那么,接下来。我们有一种面向对象的语言。因此,让我们使用它并为您的数据定义一个类,使用您提到的数据成员,以及额外的函数或运算符,以对该数据成员进行操作。只有类应该知道它的内部。在外面,我们想使用它,而不知道实现。

    在下面的示例中,extractor 和 iserter 将被覆盖,以启用流兼容的 IO 操作。在提取器中,我们还将使用regex 功能,并将准确检查输入字符串是否符合我们的预期。为此,我们使用std::regex,它准确定义了 CSV 字符串中的数据模式。然后,如果我们找到匹配项,我们就可以使用该数据。因此,这不仅是拆分字符串,而且是输入验证。这个函数是:std::regex_match

    而且,如果您查看main,读取和解析所有 CSV 数据是单行的。数据输出也一样。

    请看:

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <iterator>
    #include <algorithm>
    #include <regex>
    
    const std::regex re{R"(^\s*?(\b\w+)\s*?,\s*?(\d+\.\d+)\s*?,\s*?(\d+)\s*?$)"};
    
    // Proxy class for easy input and output
    struct MyData {
        std::string name{};
        double price{};
        long quantity{};
    
        // Overwrite extractor operator for easier input
        friend std::istream& operator >> (std::istream& is, MyData& md) {
    
            // Read a complete line from your CSV file
            if (std::string line{}; std::getline(is, line)) {
    
                // Check, if the input string matches to pour expectation, so split and validate
                if (std::smatch match{}; regex_match(line, match, re)) {
    
                    // Match is perfect. Assign the values to our internal data members
                    md.name = match[1]; md.price = std::stod(match[2]); md.quantity = std::stol(match[3]);
                }
                else // No match. Set everything to default 
                    md = {};
            }
            return is;
        }
        // Simple output of our data members
        friend std::ostream& operator << (std::ostream& os, const MyData& md) {
            return os << "Name: " << md.name << "\t\tPrice: " << md.price << "\tQuantity: " << md.quantity;
        }
    };
    
    int main() {
    
        // Open the csv File and check, if it could be opened and everything is ok
        if (std::ifstream csvStream("r:\\antiquelist.txt"); csvStream) {
    
            // Read and parse the complete source file
            std::vector myData(std::istream_iterator<MyData>(csvStream), {});
    
            // Show complete result to user
            std::copy(myData.begin(), myData.end(), std::ostream_iterator<MyData>(std::cout, "\n"));
        }
        else {
            std::cerr << "\n*** Error: Could not open input file\n";
        }
        return 0;
    }
    

    请查看here,以更好地了解正则表达式

    很遗憾没有人会读到这个。 . .

    【讨论】:

      【解决方案3】:

      您需要通过getline 使用默认的tokenizer 参数获取新的数据行,然后使用逗号',' 作为tokenizer 参数使用getline 再次解析每一行数据:

      #include <iostream>
      #include <vector>
      #include <string>
      #include <sstream>
      #include <fstream>
      using namespace std;
      
      struct Data {
          string m_name;
          float m_price;
          int m_quantity;
      };
      
      int main() {
          ifstream file("antiquelist.txt");
          string line;
          std::vector<Data> items;
          Data rowData;
      
          while (getline(file, line)) {
              stringstream splitter(line);
      
              string dataStr;
      
              getline(splitter, dataStr, ',');
              rowData.m_name = dataStr;
      
              getline(splitter, dataStr, ',');
              stringstream(dataStr) >> rowData.m_price;
      
              getline(splitter, dataStr);
              stringstream(dataStr) >> rowData.m_quantity;
      
              items.push_back(rowData);
          }
      
          return 0;
      }
      

      【讨论】:

      • 请注意以下内容。此代码执行的输入验证量少于建议的量。损坏的文件可能会导致有趣的结果。它也可能导致无聊的结果,但谁在乎呢?
      猜你喜欢
      • 2020-10-01
      • 1970-01-01
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      • 2016-05-29
      • 1970-01-01
      • 2013-03-12
      • 2014-07-25
      相关资源
      最近更新 更多