1

I am a beginner in C++ and am working on a (supposedly SIMPLE) assignment, but it is making my head hurt...

I am familiar with C# and Java, where dynamic arrays are allowed, and other neat things such as foreach loops exist...

The program consists of reading data from a file that looks like this:

5
Johnson
5000
Miller
4000
Duffy
6000
Robinson
2500
Ashton
1800

The first line is always the number of "candidates" followed by repeating name/number of votes for that candidate (each following line respectively). The program has a limit of 20 candidates - I assume this is because dynamic arrays are not allowed so we must create fixed sized arrays to hold each type of data.

I am basically looking to read the first line, store it in a variable which will be used as my loop iteration limit, and store all the names in one array then store all the numbers in a second array to treat them.

Here is a small test snippet that I made when I try to read the file. It works but it generates an infinite loop :(

int main() {
    string candidateNames[21];
    double votesReceivedPerCandidate[21];
    ifstream infile("votedata.txt");

    while (!infile.eof()) {
        string firstLine;
        int numberofCandidates;
        getline(infile, firstLine);
        numberofCandidates = atoi(firstLine.c_str());

        for (int x = 0; x < numberofCandidates; x++) {
            cout << "hello!";
        }
    }
}

I am very sleep deprived right now, and not even sure if I am thinking about this the right way.

My questions are:

  1. using C++, how would you read just the first line and then use the number from that line as a condition in a for loop? (also - is a for loop the best option for this? how would you set it up?)

  2. how would you setup the loop above to read the file and store one entry into one array, the next entry into a second array, and repeat that process in pairs? (the same way as if you deal cards to 2 people)?

Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
Vlad Pro
  • 31
  • 3
  • `while (!infile.eof())` [is always a bug](http://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong). Regarding your actual questions, [have you tried talking to your rubber duck, yet](https://en.wikipedia.org/wiki/Rubber_duck_debugging)? – Sam Varshavchik Jul 14 '16 at 02:23
  • Use std::vector for resizable arrays. If you don't know how long it will be, just use push_back. Note that ifstream::operator>> reads a word. You could think about using it insteaw of getline. Half the time it would be a word and half the time a number to parse. – bisthebis Jul 14 '16 at 02:41

2 Answers2

1

C++ has dynamic arrays (via std::vector) and foreach loops (via std::for_each(), range-based for loops, etc).

Try something more like this:

#include <vector>
#include <string>
#include <fstream>
#include <limits>

struct candidate
{
    std::string name;
    double votes;
};

int main()
{
    std::vector<candidate> candidates;

    std::ifstream infile("votedata.txt");

    int numberofCandidates;
    if (infile >> numberofCandidates)
    {
        infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        for (int x = 0; x < numberofCandidates; x++)
        {
            candidate c;
            if (!std::getline(infile, c.name)) break;
            if (!(infile >> c.votes)) break;
            infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            candidates.push_back(c);
        }
    }

    // use candidates as needed...

    return 0;
}

If you really want to be tricky, you can let the STL loop through the file for you, by utilizing std::copy() or std::copy_n() with std::istream_iterator and std::back_inserter as the input/output iterators:

#include <vector>
#include <string>
#include <fstream>
#include <algorithm>

struct candidate
{
    std::string name;
    double votes;
};

std::istream& operator>>(std::istream &in, candidate &c)
{
    if (std::getline(in, c.name))
    {
        if (in >> c.votes)
            in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    return in;
}

int main()
{
    std::vector<candidate> candidates;

    std::ifstream infile("votedata.txt");

    int numberofCandidates;
    if (infile >> numberofCandidates)
    {
        infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        // using std::copy()...

        // ignoring numberofCandidates, read until EOF...
        std::copy(std::istream_iterator<candidate>(infile),
                 std::istream_iterator<candidate>(),
                 std::back_inserter(candidates));

        // using std::copy_n()...

        std::copy_n(std::istream_iterator<candidate>(infile),
                 numberofCandidates,
                 std::back_inserter(candidates));
    }

    // use candidates as needed...

    return 0;
}

Considering that you have not covered std::vector yet, you probably have not covered STL algorithms yet either, so the above would look more like this using manual looping and 2 fixed arrays instead of a std::vector:

#include <string>
#include <fstream>
#include <limits>

const int cMaxCandidates = 20;

int main()
{
    std::string candidateNames[cMaxCandidates];
    double candidateVotes[cMaxCandidates];
    int numberofCandidates = 0;

    std::ifstream infile("votedata.txt");

    int numberofCandidatesInFile;
    if (infile >> numberofCandidatesInFile)
    {
        infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        for (int x = 0; (x < numberofCandidatesInFile) && (x < cMaxCandidates); x++)
        {
            if (!std::getline(infile, candidateNames[x])) break;
            if (!(infile >> candidateVotes[x])) break;
            infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            ++numberofCandidates;
        }
    }

    // use candidateNames[] and candidateVotes[] up to max
    // numberofCandidates of elements as needed...

    return 0;
}

Alternatively, using dynamically allocated arrays:

#include <string>
#include <fstream>
#include <limits>

int main()
{
    std::string *candidateNames = NULL;
    double *candidateVotes = NULL;
    int numberofCandidates = 0;

    std::ifstream infile("votedata.txt");

    int numberofCandidatesInFile;
    if (infile >> numberofCandidatesInFile)
    {
        infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        candidateNames = new std::string[numberofCandidatesInFile];
        candidateVotes = new double[numberofCandidatesInFile];

        for (int x = 0; x < numberofCandidatesInFile; x++)
        {
            if (!std::getline(infile, candidateNames[x])) break;
            if (!(infile >> candidateVotes[x])) break;
            infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            ++numberofCandidates;
        }
    }

    // use candidateNames[] and candidateVotes[] up to max
    // numberofCandidates of elements as needed...

    delete[] candidateNames;
    delete[] candidateVotes;

    return 0;
}
Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
  • Woah - so much help !! That was more than I expected!! thank you so much - I will try to do this and see if it works... thank you for showing me what a vector in c++ is but we did not cover that yet ... the instructions require 2 arrays and I have to pass those arrays to functions too ... I have no problem with any of that at all I know exactly what to do but how would you create those to 2 arrays from reading a file through a loop? I described it as if you are dealing cards to 2 people lol not sure if you understand what I mean... Thank you for all your help!! – Vlad Pro Jul 14 '16 at 04:11
  • A vector is a wrapper for an array, you should learn it sooner rather than later. But in any case, I have updated my answer to show examples of how to read the file into 2 arrays. – Remy Lebeau Jul 14 '16 at 04:23
  • Thank you wow! Solved... !! Any other way I can upvote you anywhere else? you just gifted me several hours of extra study time for my linear algebra exam !! :D I'm done the whole project in like 5 minutes after you posted my reply and will be happy to show you the results if you are interested to see the whole thing. – Vlad Pro Jul 14 '16 at 06:48
-1
#include <bits/stdc++.h>
using namespace std;

int main() {

        string candidateNames[21];
        double votesReceivedPerCandidate[21];
        ifstream infile("in.txt");

        int numberofCandidates;
        infile >> numberofCandidates;
        infile.get();

        for(int i = 0; i < numberofCandidates; ++i) {

            getline(infile, candidateNames[i]);
            infile >> votesReceivedPerCandidate[i];

            for (int x = 0; x < numberofCandidates; x++) {
                cout << "hello!" << endl;
            }
            cout << endl;
        }
        return 0;
 }

Get the number of candidates first, then use a for loop to run upto this number of candidates. For each number of candidate take the name and the votes like this way. For making sure everything is alright, I am printing "hello" numberofcandidates * numberofcandidates times. Hope this helps.

  • Using `infile.get()` to discard the line break after the first line is a code smell, use `std::getline()` instead. And you are not reading the line break after each vote count. And you are not validating that the file contains no more than 21 candidates, or doing any error handling at all. And do you really want to output `hello` X times for *each* candidate that is read? Shouldn't that output loop be outside of the reading loop? – Remy Lebeau Jul 14 '16 at 03:19
  • Thanks Remy - YEP should be outside (gonna go get more coffee now!!) – Vlad Pro Jul 14 '16 at 04:08