繁体   English   中英

了解如何读入文件并将文件中的行分隔到不同的变量中

[英]Understanding How to Read Into A File & Separate Lines from File into Different Variables

所以这是我的困境。 我今天要完成这个家庭作业,其中涉及从文件中读取数据,将行分隔为各种变量,并根据某些参数显示行。 我(以及我的同学)遇到的问题是我们的教授在教学方面非常糟糕,而且我们的投影仪目前坏了,她无法从预先提供的幻灯片中进行教学,而是 100% 依赖于她提出的示例并且很少解释。 不仅如此,我已经为此工作了好几个小时,现在是凌晨 4:30,不管教授是谁,我的编程都很糟糕。 我从来都不是很好,这实际上会让我改变专业,因为我无法掌握它。 基本上我只需要了解朝着正确方向采取的步骤,因为否则我将整夜不眠,并且因为它而当天会有一个悲惨的 rest。

我们的任务涉及从农场列表中获取数据,其中还包括许多项目、所述项目的描述、每件项目的价格以及所述项目的总成本乘以每件项目的成本,所有这些都在每个“完成”的一行上清单。 如果之前在文件中提到了农场本身(重复项方便地彼此相邻放置),则将项目数量以及总价格添加到一行中。 例如,在“Big Top Farm”的 3 个列表之间将显示为一行,其中包含 10,625 个总项目,总成本为 5,622.30 美元。 最后,该代码旨在打印出特定数量的贡献的“独特”农场(具有重复条目的农场仅包含一次)。 我知道我可以通过一个简单的计数器 integer 在读取特定集合后使用快速 ++ 序列来解决这个问题,但这是我唯一知道我做得正确的事情。

这是我对代码的绝望尝试(是的,我知道它未完成且未构建)

#include <fstream>
#include <cstdlib>
#include <string.h>

using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::ios;
using std::string;

//prototypes
void readIn();
int farmDisplay(int, string, double, double);


int main()
{
    string farmName, itemType;
    int itemCount, farms;
    double itemPrice, totalPrice;


    cout << "==================================================" << endl;
    cout << "=           FARMER'S MARKET INVENTORY            =" << endl;
    cout << "==================================================" << endl;

    farms = farmDisplay(itemCount, itemType, itemPrice, totalPrice);

    cout << endl;
    cout << "There were " << farms << " unique farms contributing to this week's event." << endl;

    return 0;
}

//precondition:
//postcondition:
int farmDisplay(int itemCount, string itemType, double itemPrice, double totalPrice)
{
    int counter = 0, result, prevItemCount, currentItemCount;
    string farmName, prevFarm, currentFarm;
    ifstream inFile;

    inFile.open("ASSGN6-B.txt");

    //Check for Error
    if(inFile.fail())
    {
        cout << "Error opening file..." << endl;
        exit(1);
    }

    while(!inFile.eof())
    {
        cin.ignore();

        getline(inFile, currentFarm, ',');


        if(prevFarm.compare(currentFarm) == 0)
        {
            prevFarm = currentFarm;
            prevItemCount == currentItemCount;

            counter--;
        }
        else
        {
            prevFarm = currentFarm;
            prevItemCount == currentItemCount;
        }

        inFile >> itemCount >> itemType >> itemPrice >> totalPrice;

        cout << farmName << "     " << itemCount << " items contributed totaling $" << totalPrice << endl;

        counter++;
    }
    inFile.close();

    return counter;
}

这是我们得到的文件的样子:

Collins Farm, 43900 tomatoes 0.67 29413
Bart Smith Farms, 34910 cassavas 0.99 34560.9
Allen Farms, 117 coconuts 0.54 63.18
River Run Farm, 103 taros 0.65 66.95
Big Top Farm, 109 artichokes 2.23 243.07
Big Top Farm, 777 crosns 0.28 217.56
Big Top Farm, 9739 cucumbers 0.53 5161.67
Marble Farm, 108 crosns 0.33 35.64
Food For Life Inc., 106 carrots 0.87 92.22
Food For Life Inc., 86 coconuts 0.84 72.24
Johnson Farms, 121 parsnips 0.22 26.62
A1 Farm, 111 beets 0.12 13.32
A1 Farm, 5591 taros 0.72 4025.52
Looney Tunes Farm, 102 onions 0.49 49.98
Wolfe Creek Farm, 103 rhubarbs 1.21 124.63
Wolfe Creek Farm, 199 radishes 0.71 141.29
James Farm, 47 pickles 0.68 31.96
Weaver Farms, 75 walnuts 2.5 187.5
Weaver Farms, 500 pickles 0.59 295
Pore Boy Farms, 670000 peanuts 0.79 529300
Rutherford Farms Inc., 809 apples 0.9 728.1
Rutherford Farms Inc., 659 pickles 0.7 461.3
Javens Farm, 129000 figs 0.44 56760
Harrison Farms, 8001 yams 1.09 8721.09
Setzer Farms Inc., 701 potatoes 0.89 623.89
Setzer Farms Inc., 651 tomatoes 0.69 449.19
Pikes Peak Farm, 1045 turnips 0.79 825.55
Holland Area Farms, 10001 radishes 0.69 6900.69

任何建议都将不胜感激,因为我觉得我将不再为这个项目疯狂工作

好的,我将给你一个一般的方法和一些基本的想法。 首先,编码并不容易。 这就是为什么我们这些老程序员过得很好的原因。 但你不会只是巡航。 这需要奉献精神和好奇心。 您必须喜欢它,但请考虑编写一个巨大的难题。

现在,当你被一项任务压得喘不过气来时,把任务分解成更小的部分。 你真的把它变成了一大块。

这就是我要做的。 我会制作一个 class 来表示原始数据。 我会制作一个 class 来加载文件,然后我会编写一个方法来分析数据并将其输出。

在这种情况下,从类开始。

// This holds one line's data
class LineItem {
public:
    std::string farmName;
    std::string itemName;
    int quantitySold;
    double priceEach;
    double totalPrice;

    // You'll need to implement this, see comments below.
    LineItem(const std::string fromString);
};

// This holds all the data for a specific farm.
class Farm {
public:
    std::string name;
    std::vector<LineItem *> lineItems;
};

// And this holds all the farms with the key being the farm name.
std::map<std::string, Farm *> allfarmsByName;

此时,您需要一个方法来读取数据。 我会阅读每一行,然后在分隔符(逗号和/或空格)处拆分字符串。 第 1 列是名称,第 2 列是数量,等等。

这是一段您应该能够编写的相对简单的代码。 因此,您可以获取数据行,然后执行以下操作:

 LineItem *newItem = new LineItem(myString);

如果您实现该构造函数,那么您可以这样做:

 Farm * farm = allFarmsByName[newItem->farmName];
 if (farm == nullptr) {
     farm = new Farm();
     farm->name = newItem->farmName;
     allFarmsByName.insert(pair<std::string, Farm *>(farm->name, farm)); 
 }

此时,您的 allFarmsByName class 每个贡献农场都有一个项目,每个农场都有一个包含所有数据的向量。

因此,要打印本月帮助的农场数量,您只需打印 allFarmsByName 的大小。

现在,我如何做到这一点的细节并不重要。 这是方法 把它分解。

  1. 首先,设想您的数据并构造类来表示数据。
  2. 其次,将文件中的数据读入这些对象。
  3. 然后做任何你需要做的事情来执行分析。

这是一种工作模式。 设想数据。 读取数据。 数据报告。

您已经有了一种 class 实现的方法,使用 STL std::vector将每行的每个组件保存为@JosephLarson提供的单个单元。 通过 class 实现,您可以提供成员函数来对存储的数据进行操作,以创建农场的抽象。

如果这有点超出你的学习水平,你可以通过保持两组价值观来接近它。 一个用于您正在为其收集值的当前场,另一个用于从文件中读取数据的临时集。 而不是两组各 5 个变量,任何时候您需要将不同类型的数据作为一个单元进行协调,您应该考虑structclass (两者在 C++ 中提供相同的功能,不同之处在于默认访问struct成员是public:class成员的默认值是private: .

正如我之前的评论中提到的,如果失败,您当前尝试使用while(.inFile.eof())读取数据的方法将结束。 请参阅循环条件中的Why..eof() 总是错误的。 为什么循环条件内的 iostream::eof 被认为是错误的?

相反,对于一般方法,与其尝试直接从文件流中读取对getline的不同调用的行的一部分,不如一次读取完整的行,然后从创建std::stringstream该行并从字符串流中解析您需要的信息。 这可以防止部分读取或格式错误从错误点传播到文件末尾。

该方法很简单,从您开始,但也包括<sstream> 例如,您可以包含必要的标头并声明一个简单的struct来保存每一行的不同部分,声明该结构的一个实例以在您的代码中使用,验证至少给定一个参数以提供文件名,读取文件名作为程序的第一个参数并打开文件 stream 以读取:

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

struct farmdata {
    std::string name, item;
    size_t qty;
    double cost, total;
};

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

    if (argc < 2) {
        std::cerr << "error: filename required as 1st argument.\n";
        return 1;
    }

    farmdata farm = {"", "", 0, 0.0, 0.0};
    size_t farmcount = 0;
    std::string line;
    std::ifstream f (argv[1]);
    ...

此时,而不是while(.inFile.eof()) ,通过将行实际读取到字符串line来控制您的读取循环,例如

    while (getline (f, line)) {
    ...

现在只需从行创建一个字符串流,您可以从中读取name, qty, item, cost & total而不会在解析错误或类型不匹配的情况下在stringstream中留下无关字符未读的风险。 这很简单。 您还需要声明一个结构的临时实例来读取值,这将允许您将农场的name与您正在为其收集数据的当前农场名称进行比较,例如

        std::istringstream ss (line);
        farmdata tmp;

现在只需像从文件流中一样从 stringstream ss将值读入临时结构,然后将值与farm中的当前值进行比较(请注意, farm结构被初始化为零以允许测试farm.name.length()为零表示第一行正在读入tmp ):

        if (getline (ss, tmp.name, ',')) {
            if (ss >> tmp.qty && ss >> tmp.item && ss >> 
                        tmp.cost && ss >> tmp.total) {
                if (farm.name.length() == 0) {
                    farm = tmp;
                    farmcount++;
                }
                else if (tmp.name == farm.name) {
                    farm.qty += tmp.qty;
                    farm.total += tmp.total;
                }
                else {
                    std::cout << farm.name << "  " << farm.qty << 
                        " items contributed totaling $" << farm.total << '\n';
                    farm = tmp;
                    farmcount++;
                }
            }
        }
    }

注意:只有当读入tmp的名称不同于farmcount或从文件读取的第一行时,才会更新您的农场farm.name

退出读取循环后,剩下的就是输出从文件中读取的最后一次farm的数据和farmcount本周参与的农场总数,

    std::cout << farm.name << "  " << farm.qty << 
                " items contributed totaling $" << farm.total << "\n\n";

    std::cout << "There were " << farmcount << 
            " unique farms contributing to this week's event." << '\n';

}

示例使用/输出

如果您实现与上述类似的内容,您将处理您的文件并收到类似于以下内容的内容:

$ ./bin/farmtotal dat/farms.txt
Collins Farm  43900 items contributed totaling $29413
Bart Smith Farms  34910 items contributed totaling $34560.9
Allen Farms  117 items contributed totaling $63.18
River Run Farm  103 items contributed totaling $66.95
Big Top Farm  10625 items contributed totaling $5622.3
Marble Farm  108 items contributed totaling $35.64
Food For Life Inc.  192 items contributed totaling $164.46
Johnson Farms  121 items contributed totaling $26.62
A1 Farm  5702 items contributed totaling $4038.84
Looney Tunes Farm  102 items contributed totaling $49.98
Wolfe Creek Farm  302 items contributed totaling $265.92
James Farm  47 items contributed totaling $31.96
Weaver Farms  575 items contributed totaling $482.5
Pore Boy Farms  670000 items contributed totaling $529300
Rutherford Farms Inc.  1468 items contributed totaling $1189.4
Javens Farm  129000 items contributed totaling $56760
Harrison Farms  8001 items contributed totaling $8721.09
Setzer Farms Inc.  1352 items contributed totaling $1073.08
Pikes Peak Farm  1045 items contributed totaling $825.55
Holland Area Farms  10001 items contributed totaling $6900.69

There were 20 unique farms contributing to this week's event.

简单地使用“英寸蠕虫”方法检查当前读取的名称与最后读取的名称的缺点是,您没有将数据存储在任何类型的数组或vector中,这会限制您对完整数据集进行排序或以其他方式操作以获得除“从文件中读取”以外的任何其他形式的信息。

您还可以通过包含Standard library header <iomanip> header 并使用std::setw() std::fixed setfixed-w() 和std::count.precision()和 std:count 一起进一步定制 output 格式点数格式。

如果您还有其他问题,请仔细查看并告诉我。

暂无
暂无

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

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