简体   繁体   中英

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

So here's my dilemma. I have this homework assignment due today which involves reading data in from a file, separating the lines into various variables, and displaying the lines based on certain parameters. The issue I (as well as my classmates) are experiencing is that our professor is very bad at teaching and with our projector currently broken, she's not able to teach from the pre-given slides and instead relies 100% on examples she comes up with and explains very little about. Not only this but I've been working away at this for hours, it is currently 4:30 in the morning and I am very bad at programming regardless of professor. I've never been very good and it's actually going to make me change majors because I can't get the hang of it. Basically I just need to get an idea of the steps to take in the right direction because otherwise I'm going to be up all night and will have a miserable rest of the day because of it.

Our assignment involves taking data from a list of farms which also include a number of items, the description of said item, the price per item, and the total cost of said item multiplied by the cost per item all on one line per "complete" listing. If the farm itself has been mentioned previously in the file (duplicates are conveniently placed next to each other) then add the number of items as well as the total price into one single line. So for example, between the 3 listings of "Big Top Farm" would be displayed as one line containing 10,625 total items with a total cost of $5,622.30. At the very end, the code is intended to print out a specific number of "unique" farms that contributed (ones that had repeat entries are only included once). I understand that I could go about this with a simple counter integer with a quick ++ sequence after it reads in a specific set, but that's about the only thing I know I'm doing correctly.

Here's my desperate attempt at code (which yes, I know is unfinished and doesn't build)

#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;
}

Here's what the file that we are given looks like:

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

Any advice would be greatly appreciated as I feel like I'm going to go insane working on this project any longer

Okay, I'm going to give you a general approach and some basic thoughts. First, coding isn't easy. That's why us old-time programmers have made really nice livings. But you won't just cruise into it. It takes dedication and curiosity. You have to LOVE it, but consider programming one huge puzzle.

Now, when you're overwhelmed by a task, break the task down into smaller pieces. You really kind of made it into one big piece.

Here's what I would do. I would make a class to represent the raw data. I would make a class to load the file, and then I would write a method to analyze the data and put it out.

In this case, start with the classes.

// 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;

At this point, you need a method to read the data. I would read each line and then split the string at the delimeters (commas and/or spaces). Column 1 is the name, 2 is the quantity, etc.

This is a relatively simple piece of code you should be able to write. So you can getlines of data, then do something like this:

 LineItem *newItem = new LineItem(myString);

If you implement that constructor, then you can do this:

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

At this point, your allFarmsByName class has one item per contributing farm, and each farm has a vector of all the data.

So, to print how many farms helped this month, you only need to print the size of allFarmsByName.

Now, the specifics of how I do this aren't important. It's the approach . Break it down.

  1. First, envision your data and construct classes to represent the data.
  2. Second, read the data from your file into these objects.
  3. Then do whatever you need to do to perform analysis.

This is a work pattern. Envision data. Read data. Report on data.

You already have an approach for a class implementation using the STL std::vector to hold each of the components of each line as a single unit provided by @JosephLarson . With the class implementation you can provide member functions to operate on the stored data to create an abstraction of what your farm is.

If that is a bit beyond where you are in your learning, you can approach it by keeping two sets of values. One for the current farm you are collecting values for, and a temporary set that you read data from your file into. Instead of two sets of 5 variables each, any time you need to coordinate differing types of data as a single unit, you should be thinking struct or class (both provide the same functionality in C++, the difference being that by default access to struct members are public: whereas the default for class members is private: .

As mentioned in my comments earlier, your current approach of attempting to read data with while(.inFile.eof()) will end if failure. See Why..eof() inside a loop condition is always wrong. and Why is iostream::eof inside a loop condition considered wrong?

Instead, for a general approach, rather than trying to read parts of a line with differing calls to getline directly from your file-stream, it is far better to read a complete line at a time and then to create a std::stringstream from the line and parse the information you need from the stringstream. The prevents a partial read or format error from propagating from the point of error to the end of your file.

The approach is straight forward, start as you have, but also include <sstream> . For example, you can include the necessary headers and declare a simple struct to hold the differing parts of each line, declare an instance of the struct for use in your code, validate at least one argument is given to provide a filename, read the filename as the first-argument to your program and open your file stream for reading with:

#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]);
    ...

At this point, rather than while(.inFile.eof()) , control your read loop with the actual read of the line into the string line , eg

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

Now just create a stringstream from line from which you can read name, qty, item, cost & total without any risk of leaving extraneous characters in your stream unread in case of a parsing error or type mismatch. It is quite simple to do. You will also want to declare a temporary instance of your struct to read values into that will allow you to compare the name of the farm with the current farm name you are collecting data for, eg

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

Now just read values into your temporary struct from the stringstream ss just as you would from your file-stream, and then compare values with the current values in farm (note the farm struct was initialized zero to allow a test of whether farm.name.length() is zero indicating the first line is being read into 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++;
                }
            }
        }
    }

( note: your farmcount is only updated when the name read into tmp differs from farm.name or on the first line read from the file)

After exiting your read loop all that remains is outputting the data for the last farm read from the file and output the total farmcount that participated this week,

    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';

}

Example Use/Output

If you implement something similar to the above, you would process your file and receive something similar to:

$ ./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.

The drawback to simply using a "inch-worm" method of checking the current name read against the last is you are not storing your data in any type of array or vector which limits your ability to sort or otherwise manipulate the complete data set to get the information in any other form other than "as read from the file".

You can also further tailor your output formatting by including the Standard library header <iomanip> header and making use of std::setw() for general field width along with std::fixed and std::count.precision() for floating-point number formatting.

Look things over and let me know if you have further questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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