Obviously you are using the wrong approach. This must be changed.
At first, we do need to analyze What the problem is about. So, we have a file with many lines. Each line of the many lines contains infos / values for one student. The values are delimited by a comma.
Such data is usually refered to as CSV --> comma separated values.
There are tons of posts here on SO to explain, how to read CSV files.
Anyway. After having done the initial analysis, we must start to think now, How we could solve that problem. Looking at the data in one line, we notice that it is always structured on the same way. For that reason, we define a structure, which will contain the values for one student. We call this new structure "Student". It will be defined like this:
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
Please note that the GPA will be stored as a double value and not as a string, because maybe we want to do some mathematical calculations.
The next requirement is that we have many lines with student data. So, we will store the many students in a container. And here we select the std::vector
, because it can grow dynamically.So all data for all studnets can be stored in
// Here we will store all student swith all data
std::vector<Student> student{};
Next, we want to read all data from a file. So, then let us define a filestream variable and give the filename as a constructor parameter. This will try to open the file automatically. And, if this variable will not be used any longer and falls out of scope, then the file will be closed automatically.
We check, if the file is open. Because the bool operator and the ! operator for streams is overwritten to return the status of the file, we can simply write:
if (fileStream) {
Next we want to read many lines of the file, containing student data. We will use the std::getline
function to read a complete line. And we will use this function in a while loop. The function will return a reference to the ifstream again. And, as written above, this has a bool operator. So, the reading will be stopped at EOF (End-Of_file).
while (std::getline(fileStream, line))
Then, we have a complete line in our variable "line". This needs to be split into its sub components. "SN,SM,TP,3.7,DavidLim" needs to be split into "SN", "SM","TP", 3.7, "DavidLim".
There are many many possible solutions for splitting a CSV-string. I will use an approach with std::getline
. At the bottom of this post, I show some further examples.
So, in order to use iostream facilities to extract data from a string, we can use the std::istringstream
. We will put the line into it and can then extract data as from any other stream:
std::istringstream iss{ line };
Then we will use again the std::getline
function to extract our needed data from the stringstream. And, after having extracted everything, we will add a complete Student record to our target vector:
student.push_back(tempStudent);
Now, we have all student data in our vector and we can use all function and algorithms of C++ to do all kind of operations on the data.
For the filtering, we will iterate over all data in the vector and then use an if
statement to find out, if the current student record fullfills the condition. Then we will print it.
See the following example program:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
const std::string fileName{ "r:\\DSA.txt" };
int main() {
// Here we will store all student swith all data
std::vector<Student> student{};
// Open the source file
std::ifstream fileStream{ fileName };
// Check, if file could be opened
if (fileStream) {
// One complete line of the source file
std::string line{};
// Now read all lines of the source file
while (std::getline(fileStream, line)) {
// Now we have a complete line like "SN,SM,TP,4,MarcusTan\n" in the variable line
// In order to extract from this line, we will put it in a std::istringstream
std::istringstream iss{ line };
// Now we can extract from this string stream all our needed strings
Student tempStudent{};
// Extract all data
std::getline(iss, tempStudent.first,',');
std::getline(iss, tempStudent.second,',');
std::getline(iss, tempStudent.third, ',');
std::string tempGPA{}; std::getline(iss, tempGPA, ','); tempStudent.GPA = std::stod(tempGPA);
std::getline(iss, tempStudent.name);
// Add this data for one student to the vector with all students
student.push_back(tempStudent);
}
// Now, all Students are available
// If you want to sort, then do it know. We can sort for any field.
// As an example, we sort by name. Anything else also possible
std::sort(student.begin(), student.end(), [](const Student& s1, const Student& s2) { return s1.name < s2.name; });
// Now, we make a filtered output
// Iterate over all students
for (const Student& s : student) {
// Check, if condition is fullfilled
if (s.first == "CC") {
std::cout << s.GPA << ", " << s.name << '\n';
}
}
}
else {
// There was a problem with opening the input source file. Show error message.
std::cerr << "\n\nError: Could not open file '" << fileName << "'\n\n";
}
}
But this is very C-Style. In modern C++ we would go a different way. The object oriented appoach keeps data and methods (operating on that data) together in one class or struct.
So, basically we would define an extractor and inserter operator for the struct, because only this Object should know, how to read and write its data.
Then things will be really simple and compact.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
friend std::istream& operator >> (std::istream& is, Student& s) {
char comma{};
return std::getline(std::getline(std::getline(std::getline(is, s.first,','), s.second,','), s.third,',') >> s.GPA >> comma, s.name);
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.first << '\t' << s.second << '\t' << s.third << '\t' << s.GPA << '\t' << s.name;
}
};
const std::string fileName{ "r:\\DSA.txt" };
int main() {
// Open the source file and check, if it could be opened
if (std::ifstream fileStream{ fileName }; fileStream) {
// Read the complet CSV file and parse it
std::vector student(std::istream_iterator<Student>(fileStream), {});
// Show all recors with first==CC
std::copy_if(student.begin(), student.end(), std::ostream_iterator<Student>(std::cout, "\n"), [](const Student& s) { return s.first == "CC"; });
}
return 0;
}
So, you have a one-liner for reading all student data. And then you can apply all kind of algorithms from the standard library.
That's the way to go.
Splitting a string
Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.
Alternatives
- Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
- Using old style
std::strtok
function. Maybe unsafe. Maybe should not be used any longer
std::getline
. Most used implementation. But actually a "misuse" and not so flexible
- Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.
Please see 4 examples in one piece of code.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
Please compile with C++17 enabled.
What a pity that nobody will read that.