I am trying to print out whatever is necessary from my program. What it does is it takes a long list from a text file and sort it based on first choice and GPA and put it into a vector.
I manage to sort by First choice and GPA however how can I remove whatever output that isn't necessary?
I know I asked this before but I think didn't ask correctly previously and I already edited some of it.
This is an example of my Txt File (The sequence of each line is 1st choice, 2nd choice, 3rd choice, GPA, Name):
CC,DR,TP,3.8,AlexKong
SN,SM,TP,4,MarcusTan
DR,TP,SC,3.6,AstaGoodwin
SC,TP,DR,2.8,MalcumYeo
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.2,SebastianHo
SC,TP,DR,4,PranjitSingh
DR,TP,SC,3.7,JacobMa
and so on...
This is my output now (it is a long vector):
TP,DR,SC,4,SitiZakariah
TP,DR,SC,3.9,MuttuSami
TP,DR,SC,3.5,SabrinaEster
TP,DR,SC,3,KarimIlham
TP,DR,SC,3,AndryHritik
SN,SM,TP,4,JasonTan
SN,SM,TP,3.8,MarcusOng
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.4,MollyLau
SN,SM,TP,3.2,SebastianHo
SN,SM,TP,3.2,NurAfiqah
SN,SM,TP,2.4,TanXiWei
SC,TP,DR,4,SallyYeo
SC,TP,DR,4,PranjitSingh
SC,TP,DR,3.6,RanjitSing
SC,TP,DR,2.8,MalcumYeo
SC,TP,DR,2.8,AbdulHalim
SC,TP,DR,2.7,AlifAziz
DR,TP,SC,3.9,SitiAliyah
DR,TP,SC,3.9,LindaChan
DR,TP,SC,3.8,SohLeeHoon
DR,TP,SC,3.7,PrithikaSari
DR,TP,SC,3.7,NurAzizah
DR,TP,SC,3.7,JacobMa
DR,TP,SC,3.6,AstaGoodwin
CC,DR,TP,3.9,MuruArun
CC,DR,TP,3.7,DamianKoh
CC,DR,TP,3.3,MattWiliiams
CC,DR,TP,3.3,IrfanMuhaimin
And this is the output that I need (Basically students with CC as their 1st choice without displaying the 3 options. I don't want the other options without CC as their first option. I already manage to print the output without the 3 choices as follow.):
3.9,MuruArun
3.8,AlexKong
3.7,DamianKoh
3.3,MattWiliiams
3.3,IrfanMuhaimin
This is my program:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
struct greater
{
template<class T>
bool operator()(T const &a, T const &b) const { return a > b; }
};
void main()
{
vector<string> v;
ifstream File;
File.open("DSA.txt");
if (!File.is_open()) return;
string line;
string Name;
string GPA;
string First;
string Second;
string Third;
getline(File, First, ',');
getline(File, Second, ',');
getline(File, Third, ',');
getline(File, Name, ',');
getline(File, GPA, '\n');
cout << "Round 1:\n";
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl; //remove first 3 choices from output
}
}
This is my attempt to filter out my output:
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl;
}
I thought that if I getline and make an if condition to separate CC (if the first choice is CC, then condition is true) then I only print the ones with CC as first choice and ignore the rest. so basically I try to search for CC as the first choice.
But obviously I was very wrong. So I was hoping if anyone knows how to filter the output
Previous point:
As was noted in the comment section using namespace std; is a bad choice and your code has an example of one of the reasons why that is, the redefinition of greater which is already present in the namespace.
The provided link has further explanation and alternatives.
As for you code, if the goal is to output the lines, starting with CC without the options, ordered by GPA, as I understand it, there are simpler ways of doing it, for instance, you can use std::find to parse only lines with "CC" at its beginning and work from there.
You could also use std::string::starts_with however it's only available with C++20, so I'll go with the first option.
Live demo
int main()
{
std::vector<std::string> v;
std::ifstream File;
File.open("DSA.txt");
if (!File.is_open())
return EXIT_FAILURE;
std::string line;
while (File >> line)
{
if (line.find("CC") == 0) // find lines that start with CC
v.push_back(&line[9]); // add lines without the options
} // or v.push_back(line.substr(9));
sort(v.begin(), v.end(), std::greater<std::string>()); //sort the lines
std::cout << "GPA" << "\t" << "Name" <<"\n\n"; // Title for the table
for (auto& str : v) //print the lines
{
std::replace(str.begin(), str.end(), ',', '\t'); //let's replace de comma
std::cout << str << "\n";
}
return EXIT_SUCCESS;
}
Taking your sample, this will output:
GPA Name
3.9 MuruArun
3.7 DamianKoh
3.3 MattWiliiams
3.3 IrfanMuhaimin
Lines with "CC" in second or third options will not be parsed, as is our goal.
Note:
This sorting method by string is possible and works in this case because the GPA values are lower than 10, otherwise we would have to convert the GPA field and sort the lines by its value, e.g.: 10 is larger than 9 but as a string it would be sorted first because lexicographicaly ordered, 9 would be considered larger, the character 9 is larger than the character 1.
As you can see I used the default greater template, in this case you don't need to make your own, you can just use this one.
One more thing, main must have int return type.
Note that sorting and filtering records of data is a classical task for a DBMS.
So instead of writing a program, consider loading your CSV into a DBMS of your choice (MonetDB is a nice FOSS DBMS for analytics), say into a table named people then issuing an appropriate query, e.g.
SELECT * FROM people WHERE first_choice = 'CC' ORDER BY gpa;
(that is an SQL query) to get the output you want.
Some DBMSes even work natively with CSV files, in which case you won't need to load anything, just point your DBMS at the CSV file.
Finally, and sorry for suggesting something crude, but - if you are willing to be more "manual" about this - a spreadsheet application like LibreOffice Calc or MS Excel can import the CSV; and you can use the AutoFilter functionality to only display people with CC as the first option, and sort of descending GPA using the autofilter drop-down menu on the GPA column.
PS - This is not to detract from other valid answers of course.
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::getlinefunction 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.
In Lua, I have such a function to read a file into an array:
function readFile(file)
local output = {}
local f = io.open(file)
for each in f:lines() do
output[#output+1] = each
end
f:close()
return output
end
Now in C++, I tried to write that like this:
string * readFile(file) {
string line;
static string output[] = {};
ifstream stream(file);
while(getline(stream, line)) {
output[sizeof(output)+1] = line;
}
stream.close();
return output;
}
I know you can't return arrays from functions, only pointers. So I did this:
string *lines = readFile("stuff.txt");
And it threw me the error cannot convert 'std::string {aka std::basic_string<char>} to' std::string* {aka std::basic_string<char>*}' in intialization string *lines = readFile("stuff.txt");
Can anyone tell me what is wrong here, and is there a better way to read files into arrays?
EDIT:
I'm going to be using the returned array to do value matching using a for loop. In Lua this would be written as:
for _, each in ipairs(output) do
if each == (some condition here) then
--Do Something
end
end
How can this be done in C++, using vectors (according to the answer by Jerry Coffin)?
EDIT 2:
I can't match the vectors correctly for some reason. I wrote the code in a separate test file.
int main() {
vector<string> stuff = read_pass();
cout << stuff.size() << endl;
cout << stuff[0] << endl;
if (stuff[0] == "admin") {
cout << "true";
}
else {
cout << "false";
}
return 0;
}
read_pass() looks like this:
vector<string> read_pass() {
ifstream stream("stuff.txt");
string line;
vector<string> lines;
while(getline(stream, line)) {
lines.push_back(line);
}
stream.close();
return lines;
}
And stuff.txt looks like this:
admin
why?
ksfndj
I just put it some random lines to test the code. Every time I compile and run main.cpp the output I get is
3
admin
false
So why isn't the code being matched properly?
EDIT 3:
So instead of forcing myself down the vectors method of doing things, I decided to try this instead:
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include "basefunc.h"
using namespace std;
int main() {
string storedUsrnm;
string storedPw;
string pw = "admin";
string usrnm = "admin";
ifstream usernames("usrnm.accts");
ifstream passwords("usrpw.accts");
while(getline(usernames, storedUsrnm)) {
getline(passwords, storedPw);
print("StoredUsrnm " + storedUsrnm);
print("StoredPw: " + storedPw);
if (storedUsrnm == usrnm && storedPw == pw) {
print("True!");
return 0;
}
}
print("False!");
return 0;
}
Where print() is
void print(string str) {
cout << str << endl;
}
This still prints false, at the end, and it leads me to believe that for some reason, the "admin" read by the ifstream is different from the "admin" string. Any explanations for how this is so? Or does this code not work either?
Doesn't look to me like your current code should even compile. Anyway, I'd probably do something like this:
std::vector<std::string> read_file(std::istream &infile) {
std:string line;
std::vector<std::string> lines;
while (std::getline(infile, line))
lines.push_back(line);
return lines;
}
So the basic idea here is to read a line from the file, and if that succeeded, add that line (with push_back) to the vector of results. Repeat until reading a line from the file fails. Then return the vector of all the lines to the caller.
A few notes: especially at first, it's fairly safe to presume that any use of pointers is probably a mistake. That shouldn't be taken as an indication that pointers are terribly difficult to work with, or anything like that--just that they're almost never necessary for the kinds of things most relative beginners do in C++.
Likewise with arrays--at first, assume that what you might think of as an array in some other language translates to a std::vector in C++. C++ does also have arrays, but using them can wait a while (a long while, IMO--I've been writing C++ for decades now, and virtually never use raw pointers or arrays at all).
In the interest of simplicity, I've consolidated the data into the program, so it reads the data from the stringstream, like this:
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace std;
vector<string> read_pass(istream &is) {
string line;
vector<string> lines;
while (getline(is, line)) {
lines.push_back(line);
}
return lines;
}
int main() {
istringstream input{ "admin\nwhy?\nksfndj" };
// To read from an external file, change the preceding line to:
// ifstream input{ "stuff.txt" };
vector<string> stuff = read_pass(input);
cout << stuff.size() << endl;
cout << stuff[0] << endl;
if (stuff[0] == "admin") {
cout << "true";
}
else {
cout << "false";
}
return 0;
}
At least for me, this produces:
3
admin
true
...indicating that it has worked as expected. I get the same with an external file. If you're not getting the same with an external file, my immediate guess would be that (at least the first line of) the file contains some data you're not expecting. If the problem continues, you might consider writing out the individual characters of the strings you read in numeric format, to give a more explicit idea of what you're really reading.
After a long time, I finally came up with the answer
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <map>
using namespace std;
typedef map<int, string> strArr;
strArr readFile(string file) {
ifstream stream(file);
string line;
strArr output;
while(getline(stream, line)) {
output[output.size()+1] = line;
}
stream.close();
return output;
}
It doesn't read the file into an array, but it does return a map that does basically the same thing