0

I want to be able to loop this file opening and closing to continually search for names.

The first time is no problem and output is what is expected then, when choosing y for yes, an output loop occurs.

Any ideas as to why this would happen? The logic seems more than correct.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
    string boys, girls, name;
    int rank;
    char end = 'n';
    while (end != 'y' || end != 'Y')
    {
        cout << "Enter a name to search";
        cin >> name;
        ifstream input;
        input.open("Names2016");
        if (input.fail())
            cout << "Failed to open file.\n";
        while (!input.eof())
        {
            input >> rank >> boys >> girls;
            if (boys == name)
                cout << name << " ranks " << rank << " among boys.\n";
            if (girls == name)
                cout << name << " ranks " << rank << " among girls.\n";
        }
        input.close();
        cout << "Would you like to search another name?\n"
            << "Enter Y for yes or N for no.\n";
        cin >> end;
    }
    return 0;
}
NewToThis
  • 9
  • 3
  • 1
    http://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong. – R Sahu May 19 '17 at 03:40
  • can we see your Names2016 file? – Manvir May 19 '17 at 04:17
  • I'm very new and unsure how to show you the file. It consists of a rank, a space, the boy name associate with that rank, a space, and the girl name associated with that rank then goes to a new line. I pulled the information off of https://www.ssa.gov/cgi-bin/popularnames.cgi but you will have to adjust the format to 1000 names. I am terribly sorry I'm not sure how to give you the exact file – NewToThis May 19 '17 at 04:30

2 Answers2

0

First of all, what is this supposed to mean int end = 'n'; Are you assigning an integer with a character value?!

And why are you opening the same file inside the loop. You should probably open it only once at the beginning of the program.

And the eof doesn't have what to check for, because you have to read from a file to reach its end.

  • 1
    It needs to be `end != 'y' && end != 'Y'`. Better yet, `toupper(end) != 'Y'`. – R Sahu May 19 '17 at 03:42
  • read my closing cout statement. I ask if the user would like to continue or not, therefore, my while loop should not say (end = 'y' || end = "y') – NewToThis May 19 '17 at 03:42
  • @NewToThis Corrected, sorry about that, my bad! What about `int end = 'n';` This will give an error. You can't assign an int with a character value. What you should do is`char end = 'n';` –  May 19 '17 at 03:47
  • you are so right with the int declaration. That was definitely supposed to be char end = 'n'; I am attempting to search the same file for multiple answers. It gives a lists of names and I am wanting to search it multiple times. What other function could be used to keep a loop open until the entire file has been read? – NewToThis May 19 '17 at 03:58
  • 1
    actually you can put value 'n' into an int, but it will be interpreted as the ascii value of 'n', not a character. The real problem only comes with "cin >> n", since n is an int then the input value will be interpreted through a string-to-int conversion. Any alphabetic character entered has a numeric value 0 – Jason Lang May 19 '17 at 04:01
  • @Enea Kllomollari, he's opening the file each time through the loop because each time he's searching the whole file for a specific name. Maybe they could open the file only once then use fseek to go back to the start each time through the loop, but that would perhaps be less clear. – Jason Lang May 19 '17 at 04:05
  • @JasonLang Yes I completely get that, but he could open the file once, store its elements in an array of 3 elements (because he is only reading three elements from the file) and then search that array for the value he wants and I believe that would be clear too, because generally it is not a good practice to open and close the same file inside a loop. –  May 19 '17 at 04:09
  • @JasonLang are you familiar with the rewind function? could you explain how it works or explain how fseeks works? I won't put anything into my code that I don't understand myself. – NewToThis May 19 '17 at 04:21
  • This is for [rewind functions](http://en.cppreference.com/w/cpp/io/c/rewind) and this for [fseek](http://en.cppreference.com/w/cpp/io/c/rewind) – Manvir May 19 '17 at 04:27
  • 1
    @Enea: if you look closely, he's reading line by line in the inner loop, for an unspecified number of lines. Each row is three elements, not the whole thing. So for all we know the whole thing could have millions of names, and exceed desirable memory usage if the whole thing was loaded at once. For example,about 1.2 million unique first names exist according to some google searching.That might not be desirable to have all those loaded at once.If it's known ahead of time that the dataset is small, then it would be faster to store all the names in memory,but would also have less overall benefit. – Jason Lang May 19 '17 at 09:07
  • @JasonLang I completely get it now. Thanks for the clarification. You are so right! Thank you –  May 19 '17 at 15:35
0

There are a some of things you can do to make this code better,

  1. The first is to use ifstreams and do file input/output the proper idiomatic way in a loop, don't use .eof() to check for end of file in a loop condition (the answer linked in the comments is a good place to start if you want to know why),
  2. The second thing you want to check for validity of the file with a simple if (!file) its much cleaner IMO.
  3. The third thing is, when you have a local file handle like you do in your code, then you can just let it go out of scope and let the destructor cleanup the file and close() it, it's the C++ RAII way of doing things (notice that I have removed the open() method to the constructor call (which does the same thing)
  4. Use cerr instead of cout to report errors
  5. Use char instead of int to represent characters
  6. Not a big change, but using std::toupper like advised in the other answer's comments is a good readable way to check for uppercase and lowercase values at the same time

#include <iostream>
#include <fstream>
#include <string>
#include <cctype>
using namespace std;

int main()
{
    string boys, girls, name;
    int rank;
    char end = 'n';
    while (std::toupper(end) == 'Y')
    {
        cout << "Enter a name to search";
        cin >> name;
        ifstream input{"Names2016"};

        // change here
        if (!input) {
            cerr << "Failed to open file.\n";
        }

        while (input >> rank >> boys >> girls)
        {
            if (boys == name)
                cout << name << " ranks " << rank << " among boys.\n";
            if (girls == name)
                cout << name << " ranks " << rank << " among girls.\n";
        }
        // change below, just let the file handle go out of scope and close
        // input.close();
        cout << "Would you like to search another name?\n"
             << "Enter Y for yes or N for no.\n";
        cin >> end;
    }
    return 0;
}

But you can do better on the I/O if your file isn't guaranteed to change over different iterations (in which case you probably need to make sure that there is no race anyway, so I am assuming the file does not change much). Read in the file once and save that information to be used later

#include <iostream>
#include <fstream>
#include <string>
#include <cctype>
#include <unordered_map>
#include <vector>
using namespace std;

int main()
{
    string boys_name, girls_name, name;
    int rank;
    char end = 'n';
    ifstream input{"Names2016"};
    if (!input) {  
        cerr << "Failed to open file" << endl;
    }

    // preprocess the information and store it in a map
    // making a map from string to vector because it is unclear whether
    // there is a 1-1 mapping from the name to the rank for each name
    unordered_map<string, vector<int>> boys;
    unordered_map<string, vector<int>> girls;

    while (input >> rank >> boys_name >> girls_name) {
        boys[boys_name].push_back(rank);
        girls[girls_name].push_back(rank);
    }

    while (std::toupper(end) == 'Y')
    {
        cout << "Enter a name to search";
        cin >> name;

        // use the map to do the lookup, much faster than reading 
        // the entire file over and over again            
    }
    return 0;
}
Curious
  • 19,352
  • 6
  • 45
  • 114
  • 1
    Thank you Curious for your intricate mind. The first option definitely suites my current understanding of c++ for now. However, did you mean (!input) instead of (input)? – NewToThis May 19 '17 at 04:51