2

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

anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • 3
    There seems to be quite a lot wrong with this program apart from what you are asking about. The main problem is that you have a vector of strings and all you read from the file is lines of text which you add to your vector. You never separate out the different fields. – john Feb 12 '21 at 11:02
  • isnt this the same question as this one https://stackoverflow.com/q/66167200/4117728 ? I don't understand why you marked the answer as accepted when it didnt solve your problem. The output you ask for here is the same as in the old question – 463035818_is_not_an_ai Feb 12 '21 at 11:03
  • 2
    What you need to do first, before you solve the filtering problem, is to define a `struct` with the different fields (choices, GPA, name, etc) and read the data into a vector of that struct. This will require splitting the data at each comma. It looks like you have tried to do that but didn't really understand what you were doing. Once you have completed this task the filtering will be easy. – john Feb 12 '21 at 11:04
  • 2
    There is a flood of questions about GPA homework that all looks identical to yours. What course are you taking, and where? Is your professor recommending the entire class just comes to Stack Overflow instead of attending lab classes? – paddy Feb 12 '21 at 11:10
  • @largest_prime_is_463035818 yes i know it is the same but i changed it a bit and try to do it this way. i did apply one of the answer that were given there and it solved one issue. i got one more left. – Hakim Muhaimin Feb 12 '21 at 11:11
  • but the part of filtering the input is already adressed in the other answer on your old question. The same answer could be posted here, and a complete answer to your old question would have covered what you ask here again. Nevermind, my more serious complaint is that your question could be more focused. If you want to filter a vector of strings, then reading from a file isnt the issue. However, actually that is the issue ;). Use a structure for the data, then sorting and filtering is trivial – 463035818_is_not_an_ai Feb 12 '21 at 11:18
  • 2
    btw `using namespace std;` + using names of standard types for your custom ones (`greater`) is a recipe for confusion and bugs. See here: [Why is “using namespace std;” considered bad practice?](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) – 463035818_is_not_an_ai Feb 12 '21 at 11:21

3 Answers3

1

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.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • Thank you so much Sir/Maam. Much appreciated. The code worked wonders. I am aware of some mistakes I made such as using name std. I will take that into account in the future. I also do not know that using the std::find function is so useful. I have never applied that before but I will now. Thank you for that. Take care. – Hakim Muhaimin Feb 12 '21 at 11:47
  • @HakimMuhaimin, it's sir ;) I'm glad I could help, C++ libraries are filled with out-of-the-box options for all sorts of things, unlike C, std::find is very handy indeed. The mistakes are a segway for improvement so I'd say they are very useful. – anastaciu Feb 12 '21 at 11:58
  • Hi sir. One more thing to ask. I've tried to insert a v.resize() to resize the vector and cut down the number of outputs but I got a "vector iterators incompatible" message. Do you know why or how to include the resize()? – Hakim Muhaimin Feb 12 '21 at 12:24
  • @HakimMuhaimin, since you were so nice I improved the code so it parses only the needed lines from the file, uppon reading more carefully your question I believe this is more fitting to your needs. As for your question, I would need to see the code, just ask a new question, I'll answer it, just let me know when you do. – anastaciu Feb 12 '21 at 12:29
  • 1
    Thanks again sir. Much much appreciated! :) – Hakim Muhaimin Feb 12 '21 at 12:34
  • Hi sir. Do you know what I must do in order to print the whole line without commas? My txt file contains commas to separate each variable. However, if I were to remove the commas and try to print, it does not work. It states abort() has been called. Do you know how to print without the commas? – Hakim Muhaimin Feb 13 '21 at 04:01
  • @HakimMuhaimin sure, there are several ways to do this, one of them is to use `std::replace` to replace the comma with a space, ***e.g.*** given the string `str` you could do `std::replace(str.begin(), str.end(), ',', ' ');` C++17 required. – anastaciu Feb 13 '21 at 09:44
1

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.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
einpoklum
  • 118,144
  • 57
  • 340
  • 684
0

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

  1. Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
  2. Using old style std::strtok function. Maybe unsafe. Maybe should not be used any longer
  3. std::getline. Most used implementation. But actually a "misuse" and not so flexible
  4. 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.

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