I have a list of video game characters that are organized in a certain way.
I want to be able to take their names only from the list and sort it alphabetically.
The list is formatted by:
Last Name, First Name, Game, Relase Date, Score, Developer
The list is:
Snake, Solid, Metal Gear Solid, 9/3/1998, 94, Konami
Drake, Nathan, Uncharted, 11/19/2007, 90, Naughty Dog
Guy, Doom, Doom, 5/13/1993, 95, iD
The output I want is:
Drake, Nathan
Guy, Doom
Snake, Solid
I can print out there names only in the order in which they are in the text file. How do I compare the last names, then print out the full names?
Here is my code so far:
#include <fstream>
#include <iostream>
#include <string>
#include <time.h>
#include <cstdlib>
#include <sstream>
using namespace std;
ifstream inFile;
class Characher{
private:
string first;
string last;
public:
Character(){};
void getLast(){
if(inFile.is_open()){
getline(inFile, last,',');
} else {
cout<<"Hmm.."<<endl;
}
}
void getFirst(){
if(inFile.is_open()){
getline(inFile, first,',');
} else {
cout<<"No First Name here..."<<endl;
}
}
void printLast(){
cout<<last<<",";
}
void printFirst(){
cout<<first<<endl;
}
};
class Dev{
private:
string Developer;
public:
Dev(){};//null constructor
void printDeveloper(){
cout<<"Developer: "<<Developer<<endl;
}
void getDeveloper(){
if(inFile.is_open()){
getline(inFile, Developer);
} else {
cout<<"Nothing here..."<<endl;
}
}
};
class Game{
private:
string game;
public:
Game(){};
void getGameName(){
if(inFile.is_open()){
getline(inFile, game,',');
} else{
cout<<"What game was they frum?"<<endl;
}
}
void printGame(){
cout<<"Game: "<<game;
}
};
class RelDate_And_Score{
private:
string ReleaseDate;
string Score;
public:
RelDate_And_Score(){};
void GetRelDate(){
if(inFile.is_open()){
getline(inFile, ReleaseDate, ',');
} else{
cout<<"Could not find Release Date"<<endl;}
}
void getScore(){
if(inFile.is_open()){
getline(inFile, Score, ',');
} else{
cout<<"Could not find Score"<<endl;}
}
void PrintDate(){
cout<<"Release Date: "<<ReleaseDate<<" | ";}
void PrintScore(){
cout<<"Score: "<<Score<<endl;}
};
int main(){
inFile.open("Games.dat");
Dev d;
Characher c;
RelDate_And_Score r;
Game g;
for (int i=0; i<3; i++)
{
c.getLast();
c.getFirst();
g.getGameName();
r.GetRelDate();
r.getScore();
d.getDeveloper();
c.printLast();
c.printFirst();
}
return 0;
}
I see you work directly with the file for each method (getLast() etc). This is not the best way to do it, as accessing the file is costly and less efficient.
You should construct a representation of the file in memory: Start with representing each row as a Character class such as:
class Character
{
public:
Character(const string & firstname, const string & secondname, const string & game, const string & releasedate, const string & score, const string & developer)
:
m_firstname(firstname), m_secondname(secondname), m_game(game), m_releasedate(releasedate), m_score(score), m_developer(developer)
{}
private:
string m_firstname, m_secondname, m_game, m_releasedate, m_score, m_developer;
};
Open the file, read each row, construct a Character using the parsed strings (split by commas).
As tadman proposed in a comment, you can use the std::sort method to order the Character by names. Implement the operator< in the Character class such as:
class Character
{
//...
bool operator<(const Character & c)
{
return m_firstname < c.m_firstname;
}
//...
};
So you can use the std::sort on your vector<Character> m_characters
std::sort(m_characters.begin(), m_characters.end());
Related
class Client
{
public:
Client(int id, string title, int age):
~Client();
void addTW(int id, string title, int age);
int getID() const {return id;}
string getTitle() const {return title;}
int getAge() const {return age;}
private:
int id;
string title;
int age;
};
I have two functions:
load(), which is loading input .txt file - file's having titles of movies and age you need to have in order to watch the movie (e.x. Pulp Fiction - 16) and
addTW(int id, string title, int age), which adds Movies.
So, while adding a movie, you need to type id, title and age. I want to make that you cannot add movie if you're under a certain age (e.x. 16 or whatever). Age must be re-added from the .txt file. Basically age in connected with and only title.
I've never used .txt files. So I have no idea how to start.
#include <fstream>
void Client::addTW(int id, string title, int age)
{
int i, n = tw.size();
for(i = 0;i<n;i++)
{
ToWatch* newTW = new ToWatch(id, title, age);
tw.push_back(newTW);
return;
}
}
void Client::load()
{
ifstream input;
input.open("input.txt");
if(input.fail())
{ cout<<"Failure"<<endl;}
else
{
string s;
while(input>>s)
{
cout<<s<<" ";
}
}
input.close();
}
I am not sure, if the design of your class is OK. This you can find out by yourself.
I can help you with reading the file and extracting the age for a given title:
Please see:
#include <iostream>
#include <fstream>
#include <string>
unsigned int getAgeFromFile(const std::string& title) {
// We set a default age of 0. So, if we cannot find the title in the list, then everybody can look it
unsigned int resultingAge{ 0 };
// Define an ifstream variable. Use its constructor, to open the file, then check, if open was ok
if (std::ifstream fileMovies("input.txt"); fileMovies) {
// Read all lines in the text file in a loop with std::getline. std::getline will return false,
// if we are at end-of-file or in case of some other error. Then the loop will stop
for (std::string line{}; std::getline(fileMovies, line); ) {
// So, now we have a line from the file in tour "line" variable.
// Check, if the searched title is in it
if (line.find(title) != std::string::npos) {
// Ok, we found the title in this line. Now, we need to extract the age.
// It is at the end of the line and separated by a space. So, search from the end of the line for a space
if (size_t pos{ line.rfind(' ') }; pos != std::string::npos) {
// We found a space. Now, convert the number.
resultingAge = std::stoul(line.substr(pos));
}
}
}
}
// return result or default value, if not found
return resultingAge;
}
In your addTW function you need to insert one line before the
push_back.
if (age > getAgeFromFile(title))
Hope this helps.
Compiled and tested with VS2019 and C++17
I created a post here last week but it was for a struct.
Looking to add comma delimited values in a text file to members of a struct
I have a text file here with two values, a name and a score. I have a student class that has 4 members which are seen below.
I am looking to add the values in the text file to the corresponding members in the class separating by comma.
First five rows of the students.txt file;
Nubia,Dufrene,70
Louisa,Trippe,49
Aline,Deniz,34
Shery,Munk,63
Angila,Ping,89
My current code;
studentType.h
class q1studentType
{
//Private Memebrs
std::string studentFName;
std::string studentLName;
std::string testScore;
char grade;
public:
//Constructor
q1studentType(std::string studentFName,std::string studentLName, std::string testScore);
//Get & Set
std::string const getStudentFName() { return studentFName; }
std::string const getStudentLName() { return studentLName; }
std::string getTestScore() const { return testScore; }
void setStudentFName(std::string fN) { q1studentType::studentFName = fN; }
void setStudentLName(std::string lN) { q1studentType::studentLName = lN; }
void setTestScore(std::string ts) { q1studentType::testScore = ts; }
//Member Functions
void printStudent();
int stringToInt(std::string convertMe);
//std::vector<q1studentType> initStudents();
~q1studentType();
};
studentType.cpp
q1studentType::q1studentType(std::string sFN, std::string sLN, std::string ts)
{
studentFName = sFN;
studentLName = sLN;
testScore = ts;
stringToInt(testScore);
//assignGrade();
}
main.cpp
void initstudents() {
std::vector<q1studentType> students;
std::ifstream inFile("students.txt");
for (q1studentType i;
getline(inFile, i.setStudentFName, ',')
&& getline(inFile, i.setStudentLName, ',')
&& getline(inFile, i.setTestScore)
; )
{
studentVector.push_back(i);
}
}
I think the function in the main does not work because getline doesnt accept the setMethod as a param.
I cant use the class members in the for loop eg. i.studentFName because they are private and only accessible inside the class.
What is the best approach? Should I have the initstudents in the student.cpp file or the main?
I have an array of dvd from a Video class I created
Video dvd[10];
each video has the property,
class Video {
string _title;
string _genre;
int _available;
int _holds;
public:
Video(string title, string genre, int available, int holds);
Video();
void print();
void read(istream & is, Video dvd);
int holds();
void restock(int num);
string getTitle();
~Video();
};
I'm trying to fill up this array with data from my text file where each info such as the title and genre is separated by a comma
Legend of the seeker, Fantasy/Adventure, 3, 2
Mindy Project, Comedy, 10, 3
Orange is the new black, Drama/Comedy, 10, 9
I've tried using getline(in, line, ',') but my brain halts when its time to insert each line into the dvd array.
I also created a read method to read each word separated by a whitespace but I figured thats not what I really want.
I also tried to read a line with getline, store the line in a string and split it from there but I get confused along the line.
**I can get the strings I need from each line, my confusion is in how to insert it into my class array in the while loop especially when I can only read one word at a time.
I need help on what approach I should follow to tackle this problem.
**My code
#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>
#define MAX 10
using namespace std;
class Video {
string _title;
string _genre;
int _available;
int _holds;
public:
Video(string title, string genre, int available, int holds);
Video();
void print();
void read(istream & is, Video dvd);
int holds();
void restock(int num);
string getTitle();
~Video();
};
Video::Video(string title, string genre, int available, int holds){
_title = title;
_genre = genre;
_available = available;
_holds = holds;
}
void Video::read (istream & is, Video dvd)
{
is >> _title >> _genre >> _available>>_holds;
dvd = Video(_title,_genre,_available,_holds);
}
int Video::holds(){
return _holds;
}
void Video::restock(int num){
_available += 5;
}
string Video::getTitle(){
return _title;
}
Video::Video(){
}
void Video::print(){
cout<<"Video title: " <<_title<<"\n"<<
"Genre: "<<_genre<<"\n"<<
"Available: " <<_available<<"\n"<<
"Holds: " <<_holds<<endl;
}
Video::~Video(){
cout<<"DESTRUCTOR ACTIVATED"<<endl;
}
int main(int params, char **argv){
string line;
int index = 0;
vector<string> tokens;
//Video dvd = Video("23 Jump Street", "comedy", 10, 3);
//dvd.print();
Video dvd[MAX];
dvd[0].holds();
ifstream in("input.txt");
/*while (getline(in, line, ',')) {
tokens.push_back(line);
}
for (int i = 0; i < 40; ++i)
{
cout<<tokens[i]<<endl;
}*/
if(!in.fail()){
while (getline(in, line)) {
dvd[index].read(in, dvd[index]);
/*cout<<line<<endl;
token = line;
while (getline(line, token, ',')){
}
cout<<"LINE CUT#####"<<endl;
cout<<line<<endl;
cout<<"TOKEN CUT#####"<<endl;*/
//dvd[index] =
index++;
}
}else{
cout<<"Invalid file"<<endl;
}
for (int i = 0; i < MAX; ++i)
{
dvd[i].print();
}
}
First, I would change the Video::read function into an overload of operator >>. This will allow the Video class to be used as simply as any other type when an input stream is being used.
Also, the way you implemented read as a non-static member function returning a void is not intuitive and very clunky to use. How would you write the loop, and at the same time detect that you've reached the end of file (imagine if there are only 3 items to read -- how would you know to not try to read a fourth item)? The better, intuitive, and frankly, de-facto way to do this in C++ is to overload the >> operator.
(At the end, I show how to write a read function that uses the overloaded >>)
class Video
{
//...
public:
friend std::istream& operator >> (std::istream& is, Video& vid);
//..
};
I won't go over why this should be a friend function, as that can be easily researched here on how to overload >>.
So we need to implement this function. Here is an implementation that reads in a single line, and copies the information to the passed-in vid:
std::istream& operator >> (std::istream& is, Video& vid)
{
std::string line;
std::string theTitle, theGenre, theAvail, theHolds;
// First, we read the entire line
if (std::getline(is, line))
{
// Now we copy the line into a string stream and break
// down the individual items
std::istringstream iss(line);
// first item is the title, genre, available, and holds
std::getline(iss, theTitle, ',');
std::getline(iss, theGenre, ',');
std::getline(iss, theAvail, ',');
std::getline(iss, theHolds, ',');
// now we can create a Video and copy it to vid
vid = Video(theTitle, theGenre,
std::stoi(theAvail), // need to change to integer
std::stoi(theHolds)); // same here
}
return is; // return the input stream
}
Note how vid is a reference parameter, not passed by value. Your read function, if you were to keep it, would need to make the same change.
What we did above is that we read the entire line in first using the "outer" call to std::getline. Once we have the line as a string, we break down that string by using an std::istringstream and delimiting each item on the comma using an "inner" set of getline calls that works on the istringstream. Then we simply create a temporary Video from the information we retrieved from the istringstream and copy it to vid.
Here is a main function that now reads into a maximum of 10 items:
int main()
{
Video dvd[10];
int i = 0;
while (i < 10 && std::cin >> dvd[i])
{
dvd[i].print();
++i;
}
}
So if you look at the loop, all we did is 1) make sure we don't go over 10 items, and 2) just use cin >> dvd[i], which looks just like your everyday usage of >> when inputting an item. This is the magic of the overloaded >> for Video.
Here is a live example, using your data.
If you plan to keep the read function, then it would be easier if you changed the return type to bool that returns true if the item was read or false otherwise, and just calls the operator >>.
Here is an example:
bool Video::read(std::istream & is, Video& dvd)
{
if (is.good())
{
is >> dvd;
return true;
}
return false;
}
And here is the main function:
int main()
{
Video dvd[10];
int i = 0;
while (i < 10 && dvd[i].read(std::cin, dvd[i]))
{
dvd[i].print();
++i;
}
}
Live Example #2
However, I still say that the making of Video::read a non-static member makes the code in main clunky.
I am trying to read a string line for line down a .txt file in order to initiate an array of objects using a constructor that takes a string.
The text file is written like
TransAm
Mustang
Corvette
I feel like my loop is not iterating the information I want to be set correctly. Is there an easy way of accomplishing this?
main.cc
#include <string>
#include <iostream>
#include "Car.cc"
#include <fstream>
using namespace std;
int main()
{
Car cars[3];
string STRING;
ifstream infile;
infile.open("cars.txt");
// THIS IS HOW IT'S ACHIEVED USING FOR-LOOP - Sam
for(int i = 0; i<3 && infile;++i){
getline(infile,STRING);
cars[i].setName(STRING);
}
/* THIS IS WHAT I HAD
while(!infile)
{
getline(infile,STRING);
for(int i = 0; i<sizeof(cars);i++){
cars[i].setName(STRING);
}
}
*/
infile.close();
for(int j = 0;j<sizeof(cars);j++){
cars[j].print();
}
}
Car.h
#include <string>
using namespace std;
class Car{
public:
Car();
Car(string);
string getName();
void setName(string);
void print();
private:
string name;
};
Car.cc
#include <string>
#include "Car.h"
using namespace std;
Car::Car()
{
}
Car::Car(string s)
{
setName(s);
}
void Car::setName(string s)
{
name = s;
}
string Car::getName()
{
return name;
}
void Car::print()
{
cout << name;
}
These points need to be corrected:
while (!infile) prevents you from entering the loop.
You don't need two loops.
You can modify your loop like this:
for (int i = 0; i < sizeof(cars) && getline(infile, STRING); ++i)
cars[i].setName(STRING);
Or like this:
for (int i = 0; i < sizeof(cars) && infile; ++i) {
getline(infile, STRING);
cars[i].setName(STRING);
}
Your loop does at the moment nothing if the file is correctly opened. It will only enter if the call to open was unsuccessful.
Change your loop to either
while (getline(infile,STRING))
{
//...
}
or
while (infile)
{
//...
}
As it's been said, "Change while(!infile) to while(getline(infile,STRING))" but do not forget to remove the getline(infile,STRING); afterwards.
So I have a custom class 'Book' that has a bunch of member variables, amongst which are a vector of another custom class called 'Review' and a pointer to point at that vector as I need to pass that around through function calls in a driver program. The driver program reads the details of the each book (such as title, author, publish date etc.) from a text file and inserts into a temporary 'Book' object which it then adds onto a vector of Books maintained by the driver program. Here is the code for reading from the file:
ifstream file("books.txt");
string line;
if(file.is_open())
{
while(!file.eof())
{
Book buffBook;
getline(file, line);
buffBook.setTitle(line);
getline(file, line);
buffBook.setAuthor(line);
getline(file, line);
buffBook.setPubDate(line);
getline(file, line);
buffBook.setIsbn(line);
getline(file, line);
buffBook.setCategory(line);
getline(file, line);
buffBook.setFormat(line);
getline(file, line);
buffBook.setSynopsis(line);
vectBooks.push_back(buffBook);
}
}
else
cout<<"File not found(1)!"<<endl;
file.close();
This is running inside a int main() function.
One of the functions of the driver program is to add a review, which takes in data from the user and inserts it into a temporary 'Review' object. That object is then passed onto to be inserted in to the vector of reviews for the corresponding book. Here is the code for the addReview() function:
void addReview()
{
string name = "";
string title;
Book rTemp;
cin.ignore();
cout<<"Which book would you like to rate (Title)?: ";
getline(cin, name);
name = toLow(name);
Review r;
string re, user;
int ra;
cout<<"Username (Full Name): ";
getline(cin, user);
string fname = user.substr(0, user.find_first_of(' '));
string lname = user.substr( user.find_first_of(' ') + 1, user.size());
r.setUsrFName(fname);
r.setUsrLName(lname);
cout<<"Enter rating (1-5):";
cin>>ra;
r.setRating(ra);
cout<<"Enter a short textual review: ";
cin.ignore();
getline(cin, re);
r.setReview(re);
for(unsigned int i = 0; i < vectBooks.size(); i++)
{
title = toLow(vectBooks[i].getTitle());
if(title.find(name) != string::npos)
{
vectBooks[i].getReviews()->push_back(r);
}
}
}
Now the problem is if i add a review, it adds it for all the books. In other words, when I fetch the book info for any book, the review shows on all the books. I assume this is a problem with the pointer as it seems like all the reviews are getting stored in the same vector. I am not sure where I am messing up but I have a feeling it's with the pointer some where. Any help is appreciated.
Thank You
UPDATE
The point to the title of this problem is that I am doing the assignment of the pointer to the vector of Reviews in the constructor of the Book class, of which those 2 are member variables. Code as follows for constructor:
Book::Book()
{
pointRev = &vectReviews;
}
UPDATE 2
Here is the code for the Book Class and supporting classes:
book.h
#ifndef BOOK_H_
#define BOOK_H_
#include <string>
#include <iostream>
#include <vector>
#include "review.h"
using namespace std;
class Book
{
private:
string title;
string author;
string pubDate;
string isbn;
string category;
string format;
string synopsis;
vector<Review> vectReviews;
vector<Review>* pointRev;
public:
Book::Book() : pointRev(&vectReviews) {};
string getAuthor() const;
string getCategory() const;
string getFormat() const;
string getIsbn() const;
string getPubDate() const;
string getSynopsis() const;
string getTitle() const;
vector<Review>* getReviews();
void setAuthor(string author);
void setCategory(string category);
void setFormat(string format);
void setIsbn(string isbn);
void setPubDate(string pubDate);
void setSynopsis(string synopsis);
void setTitle(string title);
friend ostream& operator <<(ostream& out, Book& book);
vector<Review> *getPointRev() const;
vector<Review> getVectReviews() const;
void setPointRev(vector<Review> *pointRev);
void setVectReviews(vector<Review> vectReviews);
};
#endif /* BOOK_H_ */
book. cpp
#include "book.h"
string Book::getAuthor() const
{
return author;
}
string Book::getCategory() const
{
return category;
}
string Book::getFormat() const
{
return format;
}
string Book::getIsbn() const
{
return isbn;
}
string Book::getPubDate() const
{
return pubDate;
}
string Book::getSynopsis() const
{
return synopsis;
}
string Book::getTitle() const
{
return title;
}
void Book::setAuthor(string author)
{
this->author = author;
}
void Book::setCategory(string category)
{
this->category = category;
}
void Book::setFormat(string format)
{
this->format = format;
}
void Book::setIsbn(string isbn)
{
this->isbn = isbn;
}
void Book::setPubDate(string pubDate)
{
this->pubDate = pubDate;
}
void Book::setSynopsis(string synopsis)
{
this->synopsis = synopsis;
}
void Book::setTitle(string title)
{
this->title = title;
}
vector<Review> *Book::getPointRev() const
{
return pointRev;
}
vector<Review> Book::getVectReviews() const
{
return vectReviews;
}
void Book::setPointRev(vector<Review> *pointRev)
{
this->pointRev = pointRev;
}
void Book::setVectReviews(vector<Review> vectReviews)
{
this->vectReviews = vectReviews;
}
vector<Review>* Book::getReviews()
{
return pointRev;
}
ostream& operator <<(ostream& out, Book& book)
{
out<<"\nTitle: "<<book.getTitle()<<endl;
out<<"Author: "<<book.getAuthor()<<endl;
out<<"Publish Date: "<<book.getPubDate()<<endl;
out<<"ISBN: "<<book.getIsbn()<<endl;
out<<"Category: "<<book.getCategory()<<endl;
out<<"Format: "<<book.getFormat()<<endl;
out<<"Synopsis: "<<book.getSynopsis()<<endl;
cout<<"\n--- Reviews ---"<<endl;
// vector<Review>* revs = book.getReviews();
for(unsigned int h = 0; h < book.getReviews()->size(); h++)
{
cout<<"Review by: "<<book.getReviews()->at(h).getUsrFName()<<" "<<book.getReviews()->at(h).getUsrLName()<<endl;
cout<<"Rating: "<<book.getReviews()->at(h).getRating()<<endl;
cout<<"Review: "<<book.getReviews()->at(h).getReview()<<endl;
}
return out;
}
review.h
#ifndef REVIEW_H_
#define REVIEW_H_
#include <string>
using namespace std;
class Review
{
private:
int rating;
string review;
string usrFName;
string usrLName;
public:
int getRating() const;
string getReview() const;
void setRating(int rating);
void setReview(string review);
string getUsrFName() const;
string getUsrLName() const;
void setUsrFName(string usrFName);
void setUsrLName(string usrLName);
};
#endif /* REVIEW_H_ */
review.cpp
#include "review.h"
int Review::getRating() const
{
return rating;
}
string Review::getReview() const
{
return review;
}
void Review::setRating(int rating)
{
this->rating = rating;
}
string Review::getUsrFName() const
{
return usrFName;
}
string Review::getUsrLName() const
{
return usrLName;
}
void Review::setUsrFName(string usrFName)
{
this->usrFName = usrFName;
}
void Review::setUsrLName(string usrLName)
{
this->usrLName = usrLName;
}
void Review::setReview(string review)
{
this->review = review;
}
From the behavior you describe, a copy-constructor is running and making two objects that point to the same vector. push_back does indeed use a copy-constructor.
But your first code snippet doesn't make a bunch of copies of the same Book, but a new Book is created on each loop iteration (and then copied into vectBooks.
If Book doesn't have a correct user-defined copy-constructor then you aren't managing pointRev correctly. From the observed behavior, I believe that you have a destructor which frees pointRev, and then the copy inside vectBooks is left with a dangling pointer. Everything after this falls into the category of undefined behavior according to the standard, meaning "anything can happen" Then the next Book happens to reuse the same memory area, so all the instances of Book end up with the same value in the wild pointer. And then updating any one changes the vector (which isn't even alive anymore) seen by all Book instances.
Why are you using a pointer to a std::vector anyway? Much better to just put the vector into the class as a direct data member, which lets the compiler construct, copy, and destruct it automatically with no additional help from you.
Of course, it's entirely possible that you've made vectReviews a global variable, and then every single book points to the same instance. That would a review show up in all books simultaneously, because they share the vector you're adding it to.