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