0

For my course, I have been assigned with making a program that will allow the user to input their name and a score, if the inputted score is greater than one of ten scores stored in a file, that score (and it's name) will be overwritten by the new score and name. However, I am lost with how I can seperate the name and score from the file (being as they are on the same line) so that I can run the int through a conditional statement.

Here is what my score file looks like:

Henry | 100
Thomas | 85
Barry | 79
James | 76
Connor | 74
Jake | 70
Sam | 66
Rory | 60
Joe | 52
Darren | 49

Say the user enters a name with a score of 75, the program should remove Darren (player with the lowest score) from the list and add the new name and score, the scores do not need to be in order according to my assignment brief.

Here is the code that I've got so far:

void enterScore()
{
    std::cout << "Please enter your name" << std::endl;
    std::string name;
    std::cin >> name;

    std::cout << "Please enter your score" << std::endl;
    int score;
    std::cin >> score;

    std::string fileNames[10];  //Array for storing all 10 of the names already in the file
    int fileScores[10];  //Array for storing all 10 of the scores already in the file

    std::fstream inoutFile("Scores.txt");

    if (inoutFile.is_open())
    {
        //Divide the names and scores
        //E.G:
        //fileName[0] = Henry    fileScore[0] = 100
        //fileName[1] = Thomas   fileScore[1] = 85

        //Loop through all array cells
        //if fileName[i] < score, then:             Assignment brief states that the scores do not need to be sorted
        inoutFile << name << " | " << score << std::endl;  //Overwrite lowest score
    }
    else
    {
        std::cout << "File could not be opened" << std::endl;
    }
}
Exaiystic
  • 11
  • 2

2 Answers2

0

I have added pseudocode to your code sample to guide you in hopefully the right direction without giving the entire homework solution.

void enterScore()
{
    std::cout << "Please enter your name" << std::endl;
    std::string name;
    std::cin >> name;

    std::cout << "Please enter your score" << std::endl;
    int score;
    std::cin >> score;

    std::string fileNames[10];  //Array for storing all 10 of the names already in the file
    int fileScores[10];  //Array for storing all 10 of the scores already in the file

    std::fstream inoutFile("Scores.txt");

    if (inoutFile.is_open())
    {
        //Loop on each line of the file, line by line
        // split the string into two parts, one before the pipe ( | ) and the other part after the pipe. There are multiple ways to do this, one would be to loop on each character of the string value and use a condition.

        //increment a counter variable

        //fileNames[myCounter] = assign first part of the string obtained above.
        //fileScores[myCounter] = assign second part of the string obtained above.


    // Rest of logic depends on precise requirement, see below
    }
    else
    {
    std::cout << "File could not be opened" << std::endl;
    }
}

If you want to overwrite only the lowest score, you will need to loop the file entirely to find the best line to replace. However, if you want to replace any lower score, you can do it with only one loop. While you read the lines in the file, as soon as you find one with a lower score, you can replace it.

That being said, to keep things simple for a start, I suggest reading the whole file, having a variable to keep the lowest value and its position in the array.

Once you have that, to keep things simple, I suggest writing a second loop where you write a file, line by line, with the values from your two arrays, after having replaced the desired value by the new values.

You can google most of my comments/pseudo code to obtain the code you need.

Dave
  • 2,774
  • 4
  • 36
  • 52
0

I would like to propose a “more” modern C++ and object oriented solution. And, a full working solution.

In C++ we usually put data, and functions operating on that data, into an object, a class. The class and only the class should know how to handle its data. No outside global function should do that.

So in your example the “name” and “score” are attributes for an object, and extracting them from a std::istream or inserting them into a std::ostream should be only known by that object and handled by it.

As a consequence, we create a class/struct, define 2 data members, namely the “name” as a std::string and the “score” as an int. Then, we overwrite the extractor operator (>>) and the inserter operator. The extractor operator is a little bit tricky, since we first read a complete line and then split it into tokens.

In C++ you file structure is called CSV (Comma Separated Value). And, reading this is a standard task. First, read the complete line, then, using a given delimiter, extract the tokens of that string. The “splitting” is also called “tokenizing” and C++ has a dedicated functionality for that purpose: std::sregex_token_iterator. This thing is an iterator. For iterating over a string, hence “sregex”. The begin part defines, on what range of input we shall operate, then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.

1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex.

We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators a parameter, and copies the data between the first iterator and 2nd iterator to the std::vector. The statement

std::vector tokens(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

defines a variable “tokens” as a std::vector of type std::string and use the so called range-constructor of the std::vector. Please note: I am using C++17 and can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").

Additionally, you can see that I do not use the "end()"-iterator explicitly.

This iterator will be constructed from the empty brace-enclosed initializer list with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.

The, after reading the line an splitting it into tokens, we check, if we have exactly 2 tokens and then store the result as “name” and “score”. So, overall, a very simple operation.

The next object that we define is a ScoreList. This contains a std::vector of the above define object as data member and some additional member functions for demo purposes. We have an

  • Overloaded inserter
  • Load from file
  • Sort the list according to the score
  • Save the file
  • Add new entry

The secrete to have only the top 10 in the list, is, to sort the list, and save only the 10 names with the highest score in it. For that we use the std::algorithm std::copy_n.

For reading the complete csv-source-text file, we also use a dedicated functionality.

First, we open the file and check, if this was OK. Then we copy the data from the input stream, using the std::istream_iterator. This iterator, with the given type as above object, will read line by line and split everything into tokens and insert the result at the back of our internal std::vector.

std::copy(std::istream_iterator<NameAndScore>(is), {}, std::back_inserter(scoreList));

Again, the very simple and short one-liner.

The other functions are also very straight forward. No need to explain in further detail.

In main, I added some driver code to show you how it could look like. Of course, you can simply add any further functionality, whatever you need.


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>

const std::string delimiter("|");
const std::regex re("\\" + delimiter);

struct NameAndScore {

    // The data

    std::string name{};
    int score{};

    // Member functions

    // Overwrite extractor operator
    friend std::istream& operator >> (std::istream& is, NameAndScore& ns) {
        // Read a comlete line
        if (std::string line; std::getline(is, line)) {
            // Split it into parts
            std::vector tokens(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
            // If we have 2 parts, then assign to our internal data
            if (2 == tokens.size()) {
                ns.name = tokens[0];
                ns.score = std::stoi(tokens[1]);
            }
        }
        return is;
    }

    // Overwrite inserter operator
    friend std::ostream& operator << (std::ostream& os, const NameAndScore& ns) {
        return os << ns.name << " " << delimiter << " " << ns.score;
    }
};


class ScoreList {
    static constexpr size_t MaxElementsInList = 10;
    std::vector<NameAndScore> scoreList{};
public:
    // Inserter operator
    friend std::ostream& operator << (std::ostream& os, const ScoreList& sl) {
        std::for_each(sl.scoreList.begin(), sl.scoreList.end(), [&os](const NameAndScore& ns) {os << ns << "\n";});
        return os;
    }

    // Read score list from disk
    void load(const std::string& filename) {

        // Clear existing score list
        scoreList.clear();

        // Open file and check if OK
        if (std::ifstream is(filename); is) {
            // Read all data from file
            std::copy(std::istream_iterator<NameAndScore>(is), {}, std::back_inserter(scoreList));
        }
    }
    // Write Scorelist to disk
    void save(const std::string& filename) {

        // Open file and check, if it is OK
        if (std::ofstream os(filename); os) {
            sort();
            // Write 10 entries to a file
            std::copy_n(scoreList.begin(), MaxElementsInList, std::ostream_iterator<NameAndScore>(os, "\n"));
        }
    }

    // Sort the score list
    void sort() {
        std::sort(scoreList.begin(), scoreList.end(), [](const NameAndScore & s1, const NameAndScore & s2) { return s2.score < s1.score; });
    }

    // Add a new enty from the user
    void addEntry() {
        std::cout << "\nPlease enter your name:     ";
        if (std::string name; std::cin >> name) {
            std::cout << "\nPlease enter your score:    ";
            if (int score; std::cin >> score)
                scoreList.emplace_back(NameAndScore({name, score }));
        }
    }
};


const std::string filenameScoreList("r:\\Scores.txt");

int main() {
    // Define an empty score list
    ScoreList sl{};

    // Load contents from file
    sl.load(filenameScoreList);

    // Show contents
    std::cout << sl;
    // Add a new entry, given by the user
    sl.addEntry();
    // Sort and save 10 top most scored data in a file
    sl.save(filenameScoreList);

    // Jsut for demo purposes. Load the file again
    // And sho the result
    sl.load(filenameScoreList);
    std::cout << sl;

    return 0;
}

I hope, I could give you an idea, how your problem could be solved . . .

A M
  • 14,694
  • 5
  • 19
  • 44