解析 CSV 并将数据存储在结构数组中?

问题描述 投票:0回答:3

我想要尝试做的是解析包含一个人的名字、姓氏和体重的 CSV 以及结构数组中的这些数据。到目前为止,这是我的代码。我能够解析并打印出 CSV 的值,但我不太确定如何将这些值存储到结构数组中。我希望能够从 person.c 访问 csv 的值,但实际上是从 vector.c 读取该值,这意味着我必须在 vector.c 中从 person.c 调用函数 readCSV() 。你们有人能帮我吗?

//vector.h
#ifndef VECTOR_H_
#define VECTOR_H_
#include "person.h"

typedef struct
{
    Person *personArray;
    int sizeArray;
    int count;
}Vector;

//person.h
#ifndef PERSON_H_
#define PERSON_H_

typedef struct
{
    const char *firstName, *lastName;
    double weight;
}Person;

//vector.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "vector.h"

void readPerson(Vector *v)
    {
        readCSV(v->personArray);
    }


//person.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "person.h"

void initialize(Vector *v)
{
    v->sizeArray = 10;
    v->count = 0;
    v->personArray = (City*) malloc(v->sizeArray*sizeof(City));

}

void readCSV(Person* person)
    {
        FILE * fp;
        char line[1024];
        int i = 0;
        fp = fopen("mycsvfile.csv","r");
        while(fgets(line,sizeof(line),fp))
        {
            person->firstName = strtok(line,",");
            person->lastName = strtok(NULL,",");
            person->weight = atof(strtok(NULL,","));
            printf("%s %s %f\n",person->firstName, person->lastName, person->weight);
    }
    fclose(fp);
}

//main.c
#include<stdio.h>
#include<stdlib.h>
#include "vector.h"

int main()
    {
        Vector people;
        initialize(&people);
        readPerson(&people);
        return 0;
    }
c csv struct
3个回答
0
投票

如下:

int readCSV(Person* person, FILE *fp){
    int state;
    char line[1024];
    if(state = fgets(line,sizeof(line), fp)){
        person->firstName = strdup(strtok(line,","));
        person->lastName  = strdup(strtok(NULL,","));
        person->weight    = atof(strtok(NULL,"\n"));
        printf("%s %s %f\n", person->firstName, person->lastName, person->weight);
    }
    return !!state;
}

void readPerson(Vector *v){
    //if(!v && !v->personArray)
    FILE *fp;
    int count = 0;
    if(NULL != (fp = fopen("mycsvfile.csv","r"))){
        while(readCSV(&v->personArray[count], fp)){
            if(++count == v->sizeArray){
                //expand array or stop reading
            }
        }
        v->count = count;
        fclose(fp);
    }
}

0
投票

您的代码有两个主要问题:

  1. 在 readCSV 中,您有一个循环来读取 CSV 中的所有行,但您正在读取的所有数据始终存储在同一个(非数组)变量中。您需要传递一个 Vector,而不是一个 Person 来读取 CSV 并使用 people->personArray[index].some_field 存储数据。
  2. 每次调用 fgets() 都会覆盖行[1024]。在再次调用 fgets() 之前,您需要复制解析后的数据。我为此使用了 strdup 。

打印在您的代码中起作用,因为您在调用 fgets() 之前打印了解析的数据。但问题 2 使您无法分离解析和打印。

此代码修复了这两个问题,并将打印移动到不同的功能,进行最少的编辑。我已将 person.c 重命名为 vector.c,因为其中的函数处理整个向量而不是单个人。

//person.h
#ifndef PERSON_H_
#define PERSON_H_

typedef struct
{
    const char *firstName, *lastName;
    double weight;
}Person;

#endif

//vector.h
#ifndef VECTOR_H_
#define VECTOR_H_
#include "person.h"

typedef struct
{
    Person *personArray;
    int sizeArray;
    int count;
}Vector;

extern void readCSV(Vector* people);
extern void printCSV(Vector* people);

#endif

//vector.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "vector.h"

void initialize(Vector *v)
{
    v->sizeArray = 10;
    v->count = 0;
    v->personArray = (Person*) malloc(v->sizeArray*sizeof(Person));

}

void readCSV(Vector* people)
{
    FILE * fp;
    char line[1024];
    int index = 0;
    fp = fopen("mycsvfile.csv","r");
    while(fgets(line,sizeof(line),fp))
    {
        people->personArray[index].firstName = strdup(strtok(line,","));
        people->personArray[index].lastName = strdup(strtok(NULL,","));
        people->personArray[index].weight = atof(strtok(NULL,","));
        ++index;
    }
    people->count = index;
    fclose(fp);
}

void printCSV(Vector* people)
{
    int i;
    for( i=0; i<people->count; ++i )
    {
            printf("%s %s %f\n",
                    people->personArray[i].firstName,
                    people->personArray[i].lastName,
                    people->personArray[i].weight);
    }
}

//main.c
#include<stdio.h>
#include<stdlib.h>
#include "vector.h"

int main()
{
        Vector people;
        initialize(&people);
        readCSV(&people);
        printCSV(&people);
        return 0;
}

该代码有效,应该可以让您继续您的工作或任务。但为了成为符合生产状态的高质量代码,还有许多问题需要解决:

  1. 释放已用内存。使用 malloc、strdup 等分配的内存在不再使用时需要释放。在这个小程序中,当 main 退出时,它将被 C 运行时释放,但是当您的代码成为更大项目的一部分时,这将是一个重要问题。现在就习惯就好了。
  2. 处理不受约束的输入。尝试使用包含 11 个条目的输入文件运行程序,它可能会崩溃或表现出未定义的行为。这是因为我们在initialize(Vector* v) 中分配了10 个条目。为了解决这个问题,您可以使用可增长的数据类型,例如链接列表。或者你可以解析该文件两次;一旦丢弃数据以确定它有多少条目,然后分配数组,然后分配第二个数组来实际读取和存储数据。
  3. 处理不正确的输入。您需要指定 CSV 文件的格式。允许空字段吗?线的最大长度?并验证输入文件的正确性,如果不解析则传递错误,而不是读取不正确的输入。如果文件不存在或无法读取会发生什么?
  4. 使您的代码通用。 readCSV(Vector* people) 应变为 readCSV(Vector*, const char* filename)。您可以使用 main(int argc, char* argv[]) 从命令行读取文件名,而不是使用常量字符串。
  5. 当心 strtok() 和 fgets() 行为!使用 fgets() 读取的行(最后一行除外)将以 ' 结尾 '。在以“83.5”结尾的行中 “最后一个标记将是”83.5 “。注意那里的回车。这可能不是您想要的。在您的代码中它可以工作,因为 atof 在 ' 处停止解析 '。如果你不想要一个 在您的令牌中,您可以使用 strtok( line_or_NULL, ", ”)
  6. 妥善处理您的向量状态。如果调用 readCSV() 两次会发生什么?如果initialize()被调用两次()会怎样?最安全的做法是检测它,因为它是一种快速检查,如果进行了不正确的调用,则使用错误代码中止程序,或者返回错误代码。
  7. 文档代码。每个函数的标题解释了它的作用、参数和返回值列出前置条件和后置条件非常重要,例如允许或不允许在同一个 Vector 上调用两次初始化。
  8. 单元测试。

0
投票
#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <limits>
#include <algorithm>
using namespace std;

const int MAX_TOPPINGS = 2;
const int MAX_DONUTS = 50;

struct donutType {
    string name;
    bool type;
    double price;
    string filling;
    string toppings[MAX_TOPPINGS];
};

ifstream getFileStream(string);
int getDonuts(ifstream&, donutType[]);
bool continueMenu(string);
void sortByPrice(donutType[], int);
int searchByName(const donutType[], int, string);
void removeDonutFromList(donutType[], int&, int);
int getCheapestDonut(const donutType[], int);
void soldDonut(donutType, donutType[], int&);
void outputSoldDonuts(ofstream&, const donutType[], int);
void displayAvailableDonuts(const donutType[], int);
string allCaps(string);

/**
 * The main function to manage the donut ordering program.
 *
 * @return The exit status of the program.
 */
int main() {
    ifstream infile;
    ofstream outfile("sold.csv");
    string filename;
    string request;
    donutType donuts[MAX_DONUTS];
    donutType soldDonuts[MAX_DONUTS];
    int amtDonuts = 0;
    int amtSold = 0;
    double total = 0;

    cout << fixed << setprecision(2);

    // gets step 1 the input stream
    infile = getFileStream("Enter a donut file: ");

    // step 2 get total donuts
    amtDonuts = getDonuts(infile, donuts);

    // step 3 print to console
    cout << "Welcome to Hank's Donut World!\n\n";
    while (true) {

        // step 4 print the available donuts to console
        displayAvailableDonuts(donuts, amtDonuts);

        //step 5 ask for user input
        string nameEnteredByUser;
        cout << "Enter donut name or cheapest: ";
        getline(cin, nameEnteredByUser);

        // step 6 check the user input choice and call function accordingly
        int donutIndex;
        transform(nameEnteredByUser.begin(), nameEnteredByUser.end(),
                nameEnteredByUser.begin(), ::tolower);

        if (nameEnteredByUser.compare("cheapest") == 0)
            donutIndex = getCheapestDonut(donuts, amtDonuts);
        else
            donutIndex = searchByName(donuts, amtDonuts, nameEnteredByUser);

        // step 7 check if the search results was empty
        if (donutIndex == -1) {
            cout << "Donut not found!\n";
            continue;
        }

        // step 8 print the selection to console
        cout << "You selected " << donuts[donutIndex].name
                << ".\nExcellent choice!\n";

        // Step 9 inserting the sold donut into solddonuts array
        total += donuts[donutIndex].price;
        soldDonuts[amtSold] = donuts[donutIndex];
        amtSold++;

        // Step 10 removing the donut from donuts array
        removeDonutFromList(donuts, amtDonuts, donutIndex);

        // Step 11 choice to continue with more purchase
        bool complete = continueMenu("Will this complete your order? ");
        if (!complete) {
            if (amtDonuts > 0)
                continue; // go to Step 4
            else
                break; // End the program if there are no available donuts left

        }

        // Step 13 sort the donuts sold by price using bubble sort
        sortByPrice(soldDonuts, amtSold);

        // Step 14
        if (outfile.is_open()) {
            outfile << "Sold," << fixed << setprecision(2) << total << "\n";
            outputSoldDonuts(outfile, soldDonuts, amtSold);
        } else {
            cerr << "Error opening output file.\n";
        }
        break; // End the program
    }

    return 0;
}

/**
 * Retrieves an input file stream for a given filename after prompting the user.
 *
 * @param msg The message to prompt the user for the filename.
 * @return An ifstream object for the specified filename.
 */
ifstream getFileStream(string msg) {
    ifstream fileStream;
    string fileName;

    while (true) {
        // Prompt user for a filename
        cout << msg;
        getline(cin, fileName);

        // Open the file stream
        fileStream.open(fileName);

        // Check if the file stream is open
        if (fileStream.is_open()) {
            break;
        } else {
            fileStream.clear(); // Clear any error flags
            cin.ignore(numeric_limits<streamsize>::max(), '\n'); // Clear input buffer
        }
    }

    return fileStream;
}

/**
 * Reads donut data from an input file stream into a struct array of donutType.
 *
 * @param infile An input file stream containing donut data.
 * @param donuts An array of donutType to store the read data.
 * @return The number of donuts read from the file.
 */
int getDonuts(ifstream &infile, donutType donuts[]) {
    const int MAX_RECORDS = 50;
    size_t MAX_CHARS = 100;
    char line[MAX_CHARS];
    int count = 0;

    // Read and ignore the header line
    infile.getline(line, MAX_CHARS);

    // Read the CSV file line by line
    while (!infile.fail() && count < MAX_RECORDS) {
        // Read the entire line into 'line'
        infile.getline(line, MAX_CHARS);

        // Create a strings to parse the line
        string lineString(line);
        if(lineString.size() < 2)
            break;
        // Tokenize the line based on commas
        size_t start = 0;
        size_t end = lineString.find(',');

        // Read name
        donuts[count].name = lineString.substr(0, end);
        //add plus one to skip the comma
        lineString = lineString.substr(end+1);

        // Read type
        end = lineString.find(',');
        donuts[count].type = (lineString.substr(0, end) == "Cake");
        lineString = lineString.substr(end+1);

        // Read filling
        end = lineString.find(',');
        donuts[count].filling = lineString.substr(0, end);
        lineString = lineString.substr(end+1);

        // Read toppings
        end = lineString.find(',');
        donuts[count].toppings[0] = lineString.substr(0, end);
        lineString = lineString.substr(end+1);

        end = lineString.find(',') ;
        donuts[count].toppings[1] = lineString.substr(0, end);
        lineString = lineString.substr(end+1);

        // Read price
        donuts[count].price = stod(lineString);

        count++;
    }
    return count;
}

/**
 * Displays a prompt and waits for user input to continue or exit.
 *
 * @param prompt The message to display as a prompt.
 * @return True if the user chooses to continue, false if the user chooses to exit.
 */
bool continueMenu(string prompt) {
    string input;

    while (true) {
        cout << prompt;
        getline(cin, input);

        // Convert the input to lowercase for case-insensitive comparison
        transform(input.begin(), input.end(), input.begin(), ::tolower);

        if (input == "no")
            return false;
        else if (input == "yes")
            return true;
        else
            cerr << "Invalid input. Please enter 'Yes' or 'No' (case insensitive).\n";

    }
}

/**
 * Sorts an array of donuts based on their prices in ascending order using bubble sort.
 *
 * @param donuts An array of donuts to be sorted.
 * @param amtDonuts The number of donuts in the array.
 */
void sortByPrice(donutType donuts[], int amtDonuts) {
    for (int i = 0; i < amtDonuts - 1; ++i) {
        for (int j = 0; j < amtDonuts - i - 1; ++j) {
            // Compare prices and swap if needed
            if (donuts[j].price > donuts[j + 1].price) {
                // Swap
                donutType temp = donuts[j];
                donuts[j] = donuts[j + 1];
                donuts[j + 1] = temp;
            }
        }
    }
}

/**
 * Searches for a donut by name in an array of donuts.
 *
 * @param donuts An array of donuts to be searched.
 * @param amtDonuts The number of donuts in the array.
 * @param name The name of the donut to be searched.
 * @return The index of the found donut if present, otherwise -1.
 */
int searchByName(const donutType donuts[], int amtDonuts, string name) {

    transform(name.begin(), name.end(), name.begin(), ::tolower);

    for (int i = 0; i < amtDonuts; ++i) {
        string donutName = donuts[i].name;
        transform(donutName.begin(), donutName.end(), donutName.begin(),
                ::tolower);
        if (donutName.compare(name) == 0)
            return i; // Return the index if the name is found
    }
    return -1; // Return -1 if the name is not found
}

/**
 * Finds the index of the cheapest donut in an array of donuts.
 *
 * @param donuts An array of donuts to be searched.
 * @param amtDonuts The number of donuts in the array.
 * @return The index of the cheapest donut if the array is not empty, otherwise -1.
 */
int getCheapestDonut(const donutType donuts[], int amtDonuts) {
    if (amtDonuts <= 0)
        return -1; // Return -1 if the array is empty

    // Initialize to maximum possible value
    double minPrice = donuts[0].price;
    // Index of the cheapest donut
    int minIndex = 0;

    for (int i = 1; i < amtDonuts; ++i) {
        if (donuts[i].price < minPrice) {
            minPrice = donuts[i].price;
            minIndex = i;
        }
    }

    return minIndex;
}

/**
 * Converts a given string to uppercase.
 *
 * @param s The input string to be converted to uppercase.
 * @return The uppercase version of the input string.
 */
string allCaps(string s) {
    string upper = s;

    for (char &c : upper)
        c = toupper(static_cast<unsigned char>(c));

    return upper;
}

/**
 * Removes a donut from the list based on the provided index.
 *
 * @param donuts The array of donuts.
 * @param amtDonuts The current number of donuts in the array.
 * @param removeIndex The index of the donut to be removed.
 */
void removeDonutFromList(donutType donuts[], int &amtDonuts, int removeIndex) {
    if (removeIndex < 0 || removeIndex >= amtDonuts) {
        cerr << "Invalid index to remove. Index out of range." << endl;
        return;
    }

    // Shift elements to fill the gap
    for (int i = removeIndex; i < amtDonuts - 1; ++i) {
        donuts[i] = donuts[i + 1];
    }

    // Decrement the amount of donuts
    amtDonuts--;
}

/**
 * Outputs information about sold donuts to an output file.
 *
 * @param outfile The output file stream.
 * @param soldDonuts The array of sold donuts.
 * @param amtSold The current number of sold donuts in the array.
 */
void outputSoldDonuts(ofstream &outfile, const donutType soldDonuts[],
        int amtSold) {

    if (!outfile.is_open()) {
        cerr << "Error: Output file is not open." << endl;
        return;
    }

    outfile << fixed << setprecision(2);
    outfile << "Name,Type,Filling,Topping1,Topping2,Price" << endl;

    for (int i = 0; i < amtSold; ++i) {
        outfile << soldDonuts[i].name << ",";
        outfile << (soldDonuts[i].type ? "Cake" : "Dough") << ",";
        outfile << soldDonuts[i].filling << ",";
        outfile << soldDonuts[i].toppings[0] << ",";
        outfile << soldDonuts[i].toppings[1] << ",";
        outfile << soldDonuts[i].price << "\n";
    }
}

/**
 * Displays the list of available donuts with their names and prices.
 *
 * @param donuts The array of available donuts.
 * @param amtDonuts The current number of available donuts in the array.
 */
void displayAvailableDonuts(const donutType donuts[], int amtDonuts) {
    cout << "List of donuts" << endl;
    cout << "---------------------------" << endl;
    int count = 0;
    for (int i = 0; i < amtDonuts; ++i)
        cout << donuts[i].name << " " << donuts[i].price << endl;

    cout << endl;
}
© www.soinside.com 2019 - 2024. All rights reserved.