-1

Input file: In each row, there's an entry that is a pair of ID - name - GPA. Values are tab-delimited.

20210001    Bill    3.61
20210002    Joe     3.21
20210003    Royce   4.32
20210004    Lucy    2.21

I have to rearrange this file sorted by the GPA (in decreasing order).

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
    
    int main() {
       ifstream inputfile("input.txt");
       ofstream outputfile("output.txt");
    
       if (inputfile.fail()) {
          cout << "Cannot open inputfile" << endl;
       }
       if (outputfile.fail()) {
          cout << "Cannot open outputfile" << endl;
       }
    
       if (inputfile.is_open()) {
          string line;
          while (getline(inputfile, line)) {
             string token;
             stringstream ss(line);
             while (getline(ss, token, '\t')) {
             
         }
         
      }
   }
   inputfile.close();
   outputfile.close();
   return 0;
}

I'm not sure what to do next.

Royce
  • 7
  • 3

4 Answers4

2

When doing I/O from/to streams (like file streams) it usually makes it easier to create a class to keep the data for each record in the file and to create overloads for operator>> (in) and operator<< (out).

Example:

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

// one line in the file could possibly have this representation in your program:
struct record {
    std::uint32_t ID;
    std::string name;
    double GPA;
};

// an overload to read one line of data from an istream (like an ifstream) using getline
std::istream& operator>>(std::istream& is, record& r) {
    if(std::string line; std::getline(is, line)) {
        std::istringstream iss(line);
        if(not (iss >> r.ID >> r.name>> r.GPA)) {
            is.setstate(std::ios::failbit);
        }
    }
    return is;
}

// an overload to write one line to an ostream (like an ofstream)
std::ostream& operator<<(std::ostream& os, const record& r) {
    return os << r.ID<< '\t' << r.name << '\t' << r.GPA << '\n';
}

With that boilerplate in place, making the actual program becomes easy.

int main() {
    std::ifstream inputfile("input.txt");

    // read all records from the file into a vector
    std::vector<record> records(
        std::istream_iterator<record>(inputfile),
        std::istream_iterator<record>{}
    );

    // sort the records according to GPA
    // if you want a decending order, just make it  return rhs.GPA < lhs.GPA;
    std::sort(records.begin(), records.end(),
        [](const record& lhs, const record& rhs) {
            return lhs.GPA < rhs.GPA;
        }
    );

    std::ofstream outputfile("output.txt");

    // put the result in the output file
    std::copy(records.begin(),
              records.end(),
              std::ostream_iterator<record>(outputfile));
}

There may be a few things in this answer that you haven't seen before. I'll list resources for those I anticipate may require some reading:

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
1

If you know exactly how many tokens are in a line:

You could simply getline() 3 times with the tab delimiter, and store the values separately.

string id;
string name;
string gpa;

getline(ss, id, '\t');
getline(ss, name, '\t');
getline(ss, gpa, '\t');

This logic would live within your loop that iterates over the lines in the file.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
RedWheeler
  • 31
  • 1
1

You could use a struct to contain all your fields:

struct user
{
    int id; string name; double point;
};

Then insert all of them into a std::vector and finally use sort() with the comp parameter to sort by points.

Code:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;

struct user
{
    int id; string name; double point;
};

vector<user> users;

void stripInfoFromString(string inp)
{
    stringstream cur(inp);
    user curUser;
    cur >> curUser.id >> curUser.name >> curUser.point;
    users.push_back(curUser);
}

bool compareUser(user x, user y)
{
    return x.point < y.point;
}

int main()
{
    string a1 = "20210001 Bill 3.61";
    string a2 = "20210002 Joe 3.21";
    string a3 = "20210003 Royce 4.32";
    string a4 = "20210004 Lucy 2.21";

    stripInfoFromString(a1);
    stripInfoFromString(a2);
    stripInfoFromString(a3);
    stripInfoFromString(a4);

    sort(users.begin(), users.end(), compareUser);
    cout << fixed << setprecision(2);
    for (user cur : users)
    {
        cout << cur.id << " " << cur.name << " " << cur.point << "\n";
    }
}

Output:

20210004 Lucy 2.21
20210002 Joe 3.21
20210001 Bill 3.61
20210003 Royce 4.32
  • I used standard input/output to minimize the code, you can simply switch for file inputs easily.

More info:

struct : https://en.cppreference.com/w/c/language/struct

sort() : https://en.cppreference.com/w/cpp/algorithm/sort

Also, see here why is using namespace std; considered bad practice.

  • 1
    Your `for` loop is a bit overkill, when you can just use `cur >> curUser.id >> curUser.name >> curUser.point;` instead. – Remy Lebeau Jun 04 '21 at 17:05
  • @RemyLebeau yeah definitely. I don't know what I'm thinking then :) –  Jun 04 '21 at 17:12
1

I would suggest defining a struct to hold the 3 tokens, and then create a std::vector holding instances of that struct for each line. You can then sort that vector on the 3rd token. You already have <vector> in your header includes.

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
    
struct entry
{
    int id;
    string name;
    double gpa;
};

int main() {
    ifstream inputfile("input.txt");
    ofstream outputfile("output.txt");
    vector<entry> entries;
    
    if (inputfile.fail()) {
        cout << "Cannot open inputfile" << endl;
    }
    if (outputfile.fail()) {
        cout << "Cannot open outputfile" << endl;
    }
    
    string line;
    while (getline(inputfile, line)) {
        istringstream iss(line);
        entry e;
        string token;

        getline(iss, token, '\t');
        e.id = stoi(token);

        getline(iss, e.name, '\t');

        getline(iss, token, '\t');
        e.gpa = stod(token);

        /* alternatively:
        iss >> e.id >> e.name >> e.gpa;
        */

        entries.push_back(e);
    }

    inputfile.close();
    outputfile.close();

    sort(entries.begin(), entries.end(),
        [](const entry &e1, const entry &e2){
            return e1.gpa > e2.gpa;
        }
    );

    for (const entry &e : entries) {
        outputfile << e.id << '\t' << e.name << '\t' << e.gpa << '\n';
    }

    return 0;
}

Demo

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770