繁体   English   中英

使用 getline 从 .csv 文件读取时遇到问题

[英]Having trouble using getline to read from a .csv file

我的程序的一部分包括我尝试从 .csv 文件中读取行并将其部分存储到结构中。 但是,当我尝试执行下面显示的代码时,控制台告诉我有一个无效的stod实例。 有了这些信息,我进入调试器,发现没有从文件中读取任何内容到我创建的虚拟变量( part1 - part4 )中,而且它们的值仍然是""

我正在阅读的 .csv 中的一个示例行是:

"Photo Editor & Candy Camera & Grid & ScrapBook,ART_AND_DESIGN,4.1,159,19M,10;000+,Free,0,Everyone,Art & Design,January 7; 2018,1.0.0,4.0.3 and up"

我只想要直到它说"159" (也就是评论数量)。

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

using namespace std;

struct App {
    std::string name;
    std::string category;
    double rating;
    int reviewNum;
};

void readData(App appList[], const int size) {

    ifstream inFile;
    inFile.open("googleplaystore.csv");

    if(inFile.fail()) {
        cout << "File open error!";
        exit(0);  //Close program
    }

    for(int appIndex = 0; appIndex < size; appIndex++) {

        string part1, part2, part3, part4, trash = "";
        //Line1
        getline(inFile, part1, ',');    //read until ,
        getline(inFile, part2, ',');
        getline(inFile, part3, ',');
        getline(inFile, part4 , ',');
        getline(inFile, trash);         //read until end of line

        appList[appIndex].name = part1;
        appList[appIndex].category = part2;
        appList[appIndex].rating = stod(part3);
        appList[appIndex].reviewNum = stoi(part4);

        if(inFile.fail()) {
            cout << "File read error!";
            exit(0);  //Close program
        }

}

int main()
{
    cout << fixed << setprecision(1);

    App appList[NUM_RECORDS] = {};

    readData(appList, NUM_RECORDS);
}

您的程序不正确的错误检查。 它尝试读取文件末尾,失败,忽略错误,然后尝试将不存在的输入字符串转换为double 这失败了,但有一个例外。

在程序尝试执行所有这些操作之后再检查文件为时已晚。

完成后立即检查每个 IO 操作是否成功。

一种流行的方法是逐行读取输入,然后 tgen 分别解析每一行,例如,如下面的代码片段。

while (std::getline(infile, instring)) {
   std::istringstream linestream (instring);
   // read the linestream
}

除了@n指出的问题 '代词' m. 如果您未能验证getline的返回,您将很快遇到尝试通过使用','作为分隔符(或任何其他非换行符)连续调用getline来解析 .csv 文件的限制。 当您指定除'\\n'以外的分隔符时, getline在尝试读取所有字段时将忽略'\\n'寻找下一个','

相反,通常从从 .csv 读取的行创建stringstream流,然后使用getline和分隔符从stringstream解析您需要的内容。 为什么? 您在stringstream只有一行数据,因此getline必须在读取最后一个字段后停止 - 因为stringstream将为空...在您的情况下,由于您没有读取所有字段,您可以使用字段计数器和临时填充数据的App实例。 您可以简单地switch(fieldcount) {...}将从 stringstream 读取的数据分离到正确的成员变量中。

您已经在使用std::string ,您也可以#include <vector>并使用std::vector<App>进行存储,而不是使用普通的旧数组。 您可以简单地将std::vector<App>作为读取函数的返回类型,并在读取 .csv 文件时填充向量,然后返回向量以在程序的其他地方使用。

将这些部分放在一起,您可以按如下方式定义读取函数:

/* function reading csv and returning std::vector<App> containing
 * first 4 fields from each line of input .csv
 */
std::vector<App> read_csv (const std::string& name)
{
    std::vector<App> v {};          /* vector of App */
    std::string line;               /* string to hold each line */
    std::ifstream fin (name);       /* input file stream */
    ...

现在只需声明App一个临时实例,从line创建一个std::stringstream ,一个std:string来保存每个字段并循环从你的 stringstream 中获取每个字段,例如

    while (std::getline (fin, line)) {      /* read entire line into line */
        App tmp;                            /* temporary instance to fill */
        size_t nfield = 0;                  /* field counter for switch */
        std::string field;                  /* string to hold each field */
        std::stringstream ss (line);        /* create stringstream from line */
        while (getline (ss, field, ',')) {  /* read each field from line */
            ...

现在你有了你的字段,只需switch你的字段计数器以将其分配给正确的成员变量,并在填充第 4字段后,将App的临时实例添加到您的向量中,例如

            switch (nfield) {                           /* switch on nfield */
                case 0: tmp.name = field; break;        /* fill name */
                case 1: tmp.category = field; break;    /* fill category */
                case 2: try {               /* convert field to double */
                        tmp.rating = stod (field);
                    }
                    catch (const std::exception & e) {
                        std::cerr << "error invalid tmp.rating: " << 
                                    e.what() << '\n';
                        goto nextline;
                    }
                    break;
                case 3: try {               /* convert field to int */
                        tmp.reviewNum = stoi (field);
                        v.push_back(tmp);
                    }
                    catch (const std::exception & e) {
                        std::cerr << "error invalid tmp.reviewNum: " << 
                                    e.what() << '\n';
                    }
                    goto nextline;   /* all done with fields, get next line */
                    break;
            }
            ...

您的读取循环中剩下的就是更新您的字段计数器nfield并提供一个标签来中断嵌套循环和switch ,例如

            nfield++;   /* increment field counter */
        }
        nextline:;      /* label for nextline */
    }

从文件中读取所有行后,只需返回您的向量:

    return v;   /* return filled vector of App */
}

你可以从main()调用你的 read 函数,类似于:

     /* fill vector of App from csv */
    std::vector<App> appdata = read_csv (argv[1]);

把它放在一个简短的例子中,您可以执行以下操作,将 googleplay .csv 中的所有想要的信息读取到App的向量中,

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

struct App {
    std::string name;
    std::string category;
    double rating;
    int reviewNum;
};

/* function reading csv and returning std::vector<App> containing
 * first 4 fields from each line of input .csv
 */
std::vector<App> read_csv (const std::string& name)
{
    std::vector<App> v {};          /* vector of App */
    std::string line;               /* string to hold each line */
    std::ifstream fin (name);       /* input file stream */

    while (std::getline (fin, line)) {      /* read entire line into line */
        App tmp;                            /* temporary instance to fill */
        size_t nfield = 0;                  /* field counter for switch */
        std::string field;                  /* string to hold each field */
        std::stringstream ss (line);        /* create stringstream from line */
        while (getline (ss, field, ',')) {  /* read each field from line */
            switch (nfield) {                           /* switch on nfield */
                case 0: tmp.name = field; break;        /* fill name */
                case 1: tmp.category = field; break;    /* fill category */
                case 2: try {               /* convert field to double */
                        tmp.rating = stod (field);
                    }
                    catch (const std::exception & e) {
                        std::cerr << "error invalid tmp.rating: " << 
                                    e.what() << '\n';
                        goto nextline;
                    }
                    break;
                case 3: try {               /* convert field to int */
                        tmp.reviewNum = stoi (field);
                        v.push_back(tmp);
                    }
                    catch (const std::exception & e) {
                        std::cerr << "error invalid tmp.reviewNum: " << 
                                    e.what() << '\n';
                    }
                    goto nextline;   /* all done with fields, get next line */
                    break;
            }
            nfield++;   /* increment field counter */
        }
        nextline:;      /* label for nextline */
    }

    return v;   /* return filled vector of App */
}

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

    if (argc < 2) {
        std::cerr << "error: insufficient input\n" <<
                     "usage: " << argv[0] << " <file>\n";
        return 1;
    }

    /* fill vector of App from csv */
    std::vector<App> appdata = read_csv (argv[1]);

    for (auto& v : appdata)     /* output results */
        std::cout << "\nname     : " << v.name
                  << "\ncategory : " << v.category
                  << "\nrating   : " << v.rating
                  << "\nreviewNum: " << v.reviewNum << '\n';
}

示例使用/输出

使用文件dat/googleplay.csv提供的一行输入,您将收到程序的以下输出:

$ ./bin/read_googlecsv dat/googleplay.csv

name     : Photo Editor & Candy Camera & Grid & ScrapBook
category : ART_AND_DESIGN
rating   : 4.1
reviewNum: 159

读取每一行并使用std::stringstream从逗号分隔值解析您的字段可以解决您在需要利用所有字段时将面临的许多问题。 它还允许在每次调用getline使用整行输入,防止在文件出现格式问题时部分读取一行。 仔细查看并考虑使用App向量而不是普通的旧数组的优势。 如果您还有其他问题,请告诉我。

尽管已经接受了答案,但我想展示一种更“现代”的 C++ 方法。

如果您可以研究此解决方案并尝试在将来使用某些功能,我会很高兴。

在面向对象的世界中,我们使用类(或结构)并将数据和函数放在一个(封装的)对象中,对这些数据进行操作。

只有类应该知道如何读取和写入其数据。 不是一些外部的全局函数。 因此,我在您的结构中添加了 2 个成员函数。 我已经覆盖了插入器和提取器操作符。

在提取器中,我们将使用现代 C++ 算法将字符串拆分为标记。 为此,我们有std::sregex_token_iterator 并且因为有一个专门用于此目的的函数,所以我们应该使用它。 此外,它非常简单。

使用下面的单行代码,我们将完整的字符串拆分为标记并将生成的标记放入std::vector

std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});

然后我们将结果数据复制到我们的成员变量中。

对于演示输出,我还覆盖了插入操作符。 现在您可以将提取器和插入器运算符(“>>”和“<<”)用于 App 类型的变量,就像任何其他 C++ 整数变量一样。

总的来说,我们还使用了一种超简单的方法。 首先,我们打开文件并检查是否正常。

然后我们定义一个变量“apps”(App 的std::vector )并使用它的范围构造函数和std::istream_operator来读取完整的文件。 而且,由于应用程序有一个被覆盖的提取器操作符,它知道如何读取并将为我们解析完整的 CSV 文件。

再次,非常简单和短的单线

std::vector apps(std::istream_iterator<App>(inFile), {});

将读取完整的源文件,所有行,解析行并将成员变量存储在结果std::vector的单个 App 元素中。

请参阅下面的完整示例:

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

std::regex delimiter{ "," };

struct App {
    // The data. Member variables
    std::string name{};
    std::string category{};
    double rating{};
    int reviewNum{};

    // Overwrite extractor operator
    friend std::istream& operator >> (std::istream& is, App& app) {

        // Read a complete line
        if (std::string line{}; std::getline(is, line)) {
            // Tokenize it
            std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});
            // If we read at least 4 tokens then assign the values to our struct
            if (4U <= token.size()) {
                // Now copy the data from the vector to our members
                app.name = token[0]; app.category = token[1]; 
                app.rating = std::stod(token[2]); app.reviewNum = std::stoi(token[2]);
            }
        }
        return is;
    }

    // Overwrite inserter operator
    friend std::ostream& operator << (std::ostream& os, const App& app) {
        return os << "Name:     " << app.name << "\nCategory: " << app.category
            << "\nRating:   " << app.rating << "\nReviews:  " << app.reviewNum;
    }
};

int main() {

    // Open file and check, if it could be opened
    if (std::ifstream inFile("googleplaystore.csv"); inFile) {

        // Define the variable and use range constructor to read and parse the complete file
        std::vector apps(std::istream_iterator<App>(inFile), {});

        // Show result to the user
        std::copy(apps.begin(), apps.end(), std::ostream_iterator<App>(std::cout, "\n"));
    }
    return 0;
}

可惜没有人会读到这个。 . .

免责声明:这是纯示例代码,效率不高,因此没有错误处理。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM