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.
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'm looking for a way to find the number of items in a .txt file.
The file structure is as follows:
students.txt pricem 1441912123
house.pdf jatkins 1442000124
users.txt kevin_tomlinson 1442001032
accounts.mdb kevin_tomlinson 1442210121
vacation.jpg smitty83 1442300125
calendar.cpp burtons 1442588012
The result should be 18 in this example since there are 18 separate "words" in this file.
I need that value so I can iterate through the items and assign them to an array of structures (maybe there's a way to accomplish both of these steps together?):
// my structure
struct AccessRecord
{
string filename;
string username;
long timestamp;
};
// new instance of AccessRecord
// max possible records: 500
AccessRecord logRecords[500];
// while file has content
while (!fin.eof())
{
// loop through file until end
// max possible records: 500
for (int i = 0; i < 500; i++) // need to figure out how to iterate
{
fin >> logRecords[i].filename
>> logRecords[i].username
>> logRecords[i].timestamp;
}
}
Which will then be written to the screen.
So the question is, how do I find the count? Or is there a better way?
You know that each line contains a string, a string and a long, so you can iterate with:
std::vector<AccessRecord> logs;
std::string fname, uname;
long tstamp;
while(fin >> fname >> uname >> tstamp) {
logs.push_back(AccessRecord(fname, uname, tstamp));
//To avoid copies, use: (thanks #Rakete1111!)
//logs.emplace_back(std::move(fname), std::move(uname), tstamp);
}
This is assuming you've created a constructor for your struct like:
AccessRecord(std::string f, std::string u, long t)
: filename(f), username(u), timestamp(t) { }
Notice that I'm using an std::vector here instead of an array so that we don't even have to worry about the number of items, since the vector will resize itself dynamically!
You should overload operator>> for your structure:
struct AccessRecord
{
string filename;
string username;
long timestamp;
friend std::istream& operator>>(std::istream& input, AccessRecord& ar);
};
std::istream& operator>>(std::istream& input, AccessRecord& ar)
{
input >> ar.filename;
input >> ar.username;
input >> ar.timestamp;
return input;
}
This allows you to simplify your input function:
AccessRecord ar;
std::vector<AccessRecord> logs;
//...
while (fin >> ar)
{
database.push_back(ar);
}
Usually, if you are accessing an objects data members directly outside of the class or structure, something is wrong. Search the internet for "data hiding", "c++ encapsulation" and "c++ loose coupling".
I have two functions that read files and initialize variables containing data parsed from the files read.
These variables include several vectors, counters (line counts) and a few singular variables (string and ints).
The problem I am having is that these variables all need to be accessed in later functions, and the idea is to avoid global variables. Since the functions are void, they cannot return variables, and I have found (unlike my normal language of Python) returning multiple variables is difficult.
What is a better way to go about this?
The vectors in each of the read*() functions need to be accessed in a new function I am building. But I also need the num* variables, and the recipe & serving variables.
EDIT: My code currently
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
void readNutrients(string input_file) {
ifstream in(input_file.c_str());
string line;
vector<string> nName, nUnits;
vector<double> nAmount, nCalories;
string name, units;
double amount, calories;
int numNut = 0;
while (getline(in, line)) {
numNut++;
int pos = line.find(';');
name = line.substr(0, pos);
nName.push_back(name);
line = line.substr(pos + 1);
istringstream iss(line);
iss >> amount >> units >> calories;
nAmount.push_back(amount);
nUnits.push_back(units);
nCalories.push_back(calories);
}
}
void readRecipe(string input_file) {
ifstream in(input_file.c_str());
string line;
string recipe;
vector<string> rName, rUnits;
vector<double> rAmount;
string name, units;
double amount;
double servings;
int numIng = 0;
while (getline(in, line)) {
numIng++;
if (numIng == 1) {
int pos = line.find('\n');
recipe = line.substr(0, pos);
}
else if (numIng == 2) {
istringstream iss(line);
iss >> servings;
}
else {
istringstream iss(line);
iss >> amount >> units >> ws;
rAmount.push_back(amount);
rUnits.push_back(units);
getline(iss, name);
rName.push_back(name);
}
}
}
void readFiles(string nutrientFile, string recipeFile) {
readNutrients(nutrientFile);
readRecipe(recipeFile);
}
int main(int argc, char** argv) {
readFiles(argv[1], argv[2]);
return 0;
}
Since you included your code, I have a better idea of what's going on.
What you need is to create a structure that can hold the result of your parsing. Since your function is not returning anything, it's only logical that you won't have access to it's result.
I think your intent here is to have a list of nutrients read from a file, and read every nutrients from that file and fill up the list in your program.
The problem is that your program has no idea of what makes a nutrient a nutrient. You should teach him that by declaring what makes a nutrient a nutrient:
struct Nutrient {
std::string name, unit;
double amount, calories;
};
Then, instead of creating a bunch of lists of values, you should create a list of nutrients.
std::vector<Nutrient> readNutrients(std::string input_file) {
// Here declare your vector:
std::vector<Nutrient> nutrients;
// declare line, calories, name...
while (std::getline(in, line)) {
// fill your variables name calories etc...
// create a nutrient
Nutrient n;
// fill the nutrient with values from the parsing.
n.name = name;
n.unit = units;
n.amount = amount;
n.calories = calories;
// add the nutrient to the list.
nutrients.push_back(n);
}
// return a filled list of nutrient.
return nutrients;
}
By the way, you don't need the num* variables, since nutrients.size() will return you the number of nutrients in the list.
That solution goes the same with recipes: Create a type to add the concept of a recipe in your program, and use that type.
Please note that this code is not optimal, std::move from C++11 should will grant you enormous speed up.
I don't understand your case clearly. But because you can't get result as return values of void function, it may get results by output arguments using pointers or refrence types.
for example:
void _read(const char* file, vector<string>& r_list, int* pState)
{
// do parsing file
// do outputs
*pState = (your_number);
r_list.push_back("your string");
}
Hope this is useful for you.
I am reading the data with different variables by the following codes, currently when the program touches missing values (represented in data by string "NA", it will change them to zero. Alternatively, I wonder if how can we remove entire rows when program touch "NA". I have tried to look for the same question but they all are for R, not C++. Please, if you can give me some advises. Thanks
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
struct Data {
vector<double> cow_id;
vector<double> age_obs;
vector<double> dim_obs;
vector<double> my_obs;
vector<double> mcf_obs;
vector<double> mcp_obs;
vector<double> mcl_obs;
vector<double> bw_obs;
vector<double> bcs_obs;
double get_number (string value)
{
if (value == "NA")
{return 0.0;}
else
{
istringstream iss (value);
double val;
iss>>val;
return val;
}
}
void read_input (const string filepath)
{
ifstream data_in (filepath.c_str());
if (!data_in)
{cout<<"Failed to open"<<endl;}
else
{
// Read tokens as strings.
string id, age, dim, my, mcf, mcp, mcl, bw, bcs;
string dummy_line;
getline(data_in, dummy_line);
string line;
while (data_in >> id >> age >> dim >> my >> mcf >> mcp >> mcl >> bw >> bcs)
{
// Get the number from the string and add to the vectors.
cow_id.push_back(get_number(id));
age_obs.push_back(get_number(age));
dim_obs.push_back(get_number(dim));
my_obs.push_back(get_number(my));
mcf_obs.push_back(get_number(mcf));
mcp_obs.push_back(get_number(mcp));
mcl_obs.push_back(get_number(mcl));
bw_obs.push_back(get_number(bw));
bcs_obs.push_back(get_number(bcs));
}
data_in.close();
}
size_t size=age_obs.size();
for (size_t i=0; i<size; i++)
{
cout<<cow_id[i]<<'\t'<<age_obs[i]<<'\t'<<dim_obs[i]<<'\t'<<my_obs[i] <<'\t'<<mcf_obs[i]<<'\t'<<mcp_obs[i]<<'\t'<<mcl_obs[i]<<'\t'<<bw_obs[i] <<'\t'<<bcs_obs[i]<<endl;
}
};
int main()
{
Data input;
input.read_input("C:\\Data\\C++\\learncpp\\data.txt");
}
Let's talk tables here.
Tables are containers of records (rows). The data you are capturing from your input file is already organized into records. So the obvious model is to use a structure that matches your file's data records.
struct Record
{
unsigned int cow_id;
unsigned int age_obs;
unsigned int dim_obs;
// ...
};
Your table could be represented as:
std::vector<record> my_table;
So to remove a record from the table, you can use the std::vector::erase() method. Easy. Also, you can use the std::find() function to search the table.
Let's relieve some reader's headaches with your present code by introducing a concept of the record loading its members from the file.
Reading a record from a file is best performed by overloading the stream extraction operator>>:
struct Record
{
//...
friend std::istream& operator>>(std::istream& input, Record& r);
};
std::istream&
operator>>(std::istream& input, Record& r)
{
std::string record_text;
std::getline(input, record_text);
// Extract a field from the record text and check for NA,
// Assign fields of r to those values:
r.cow_id = value;
// Etc.
return input;
}
With the overloaded operator, your input looks like:
Record r;
while (input_file >> r)
{
table.push_back(r);
}
Elegant and simple (reducing injection of defects).
In my program I am trying to take from the user lines of input actually names then storing them into a vector.
I wrote my own code but I got a runtime error telling me that "string subscript out of range".
This is my code
const int LEN = 100;
struct Case{
public:
int No_People;
vector<string> Names;
vector<string> Results;
void Set_Data(){
cin >> No_People;
int Size = No_People;
char Line[LEN];
for (int i = 0; i < Size; i++){
cin.getline(Line, LEN);
Names.push_back(Line);
}
}
}
Personally I would define a class to represent a line. Then you can use stream iterators to load the vector.
class Line
{
std::string line;
public:
// Operator to convert a line back to a std::string
operator std::string const&() const {return line;}
// Friend function to read a line from a stream.
friend std::istream& operator>>(std::istream& in, Line& data)
{
return std::getline(in, data.line);
}
};
int main()
{
int countOfPeople;
std::cin >> countOfPeople;
std::vector<std::string> lines;
std::copy_n((std::istream_iterator<Line>(std::cin)), countOfPeople,
std::back_insert_iterator(lines));
}
There's no need to use a char[] array, use std::string instead, especially given that you already are using it.
Note to OP: cin.getline() is this one:
std::istream::getline(char*, int)
The one you ned to use for std::string's is this one:
std::getline(istream&, string&)
struct Case{
public:
int Size;
vector<string> Names;
vector<string> Results;
void Set_Data(){
std::string temp;
cin >> Size; cin.ignore();
for (int i = 0; i < Size; i++){
std::getline(cin, temp);
Names.push_back(temp);
}
}
}
As far as compile errors go, always:
quote the exact error messgae
tell the line it happened at
show the code that contains the line and the relevant classes/methods
Most probably you are accessing the string using subscript which is out of index. It will be easy to answer if you point at which line you are getting the error.