簡體   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