In this code I have an overloaded constructor Record::Record(string s)that reads in a string, I am trying to create a string stream from 's' and use getline(stringStream, line, ",") to read each element from the string with "," as the delimiter, then assign each element to the appropriate object variable. The end goal of this assignment is to open a file, read in its data, assign the data appropriately in a vector, then write and parse the data to a new file.
My understanding of working with private class members is limited. I am unsure how to go about writing the constructor. In the past I've used a pointer for private members (e.g 'this-> foo;), at this point I just need to understand how to implement the contents of Record, so far what I've tried has been incorrect and I can only find references to using pointers to int's.
Normally I would go to my comp-sci lab and ask a TA but it is currently close due to COVID.
Here is the code for my constuctors and overloaded operators.
Record.cpp
#include <string>
#include <sstream>
#include "Record.h"
using namespace std;
Record::Record(string s) {
/** here is where I need to assign data to the following.
std::string department;
std::string item_code;
int quantity;
double cost; **/
}
Record::~Record() {
// TODO Auto-generated destructor stub
}
//overloaded "==" and "<" comparison operators
bool operator ==(const Record &lhs, const Record &rhs){
return (lhs.cost == rhs.cost && lhs.department == rhs.department &&
lhs.item_code == rhs.item_code && lhs.quantity == rhs.quantity);
}
/**bool operator <(const Record &a, const Record &b){ //do not need at this time
}
**/
//Overloaded "<<" operator
std::ostream& operator <<(std::ostream& os, const Record& r){
os << r.department << ',' << r.item_code << ',' << r.quantity << ',' << r.cost;
return os;
}
Record.h
#ifndef RECORD_H_
#define RECORD_H_
#include <iostream>
#include <string>
class Record {
public:
//Constructor
Record(std::string s); //pass this string to our Record class
//De-constructor
virtual ~Record();
//overloaded "==" and "<" comparison operators
friend bool operator ==(const Record &a, const Record &b);
//friend bool operator <(const Record &a, const Record &b); //Do not need at this time.
//Overloaded "<<" operator
friend std::ostream& operator <<(std::ostream&, const Record&);
private:
std::string department;
std::string item_code;
int quantity;
double cost;
};
#endif /* RECORD_H_ */
Main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <libgen.h>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include "Record.h"
using namespace std;
int main() {
vector<Record> records; //vector of type Records to hold each "Line" of input file
string filename; // File name and path stored as string
/**
* Prompts user for the name of input file and stores in string variable filename
*
*/
cout << "please enter the name of your file with file path:" << endl;
cin >> filename;
ifstream ifs { filename.c_str() };
if (!ifs) {
cerr << " Can't open file " << filename << endl;
return 1;
}
string path = filename.substr(0, filename.find_last_of("\\/"));
string file = filename.substr(filename.find_last_of("\\/") + 1,
filename.length());
if (path.compare(file) == 0) {
path == "";
}
//test for file and file path
cout << "Path portion of " << filename << " is " << path << endl;
cout << "File portion of " << filename << " is " << file << endl; // path + "new_" + file + ".cvs", make new file with new path
/**
* Put each line of input file in to the records vector
*/
string line; //strings for each parameter of the vector object
while (getline(ifs, line)) {
Record newRecord(line); //this should check for duplicates and ignore any found.
int i = 0;
int n = 0;
if((newRecord == records[i]) || (i < n) ){
i++;
}
else{
records.push_back(newRecord);
n++;
i = 0;
}
}
ifs.close(); //closes the stream
//create new file and output data to it
string newFile = ("new_" + file + ".cvs");
//check to see if file path and file name are correct
cout << (path + newFile);
//Open stream to new file and write to it
ofstream ofs(path + newFile);
ofs.open(newFile);
for(size_t i = 0; i < records.size(); i++){
ofs << (i+1) << ' ' << records[i];
}
ofs.close(); //close output stream
return 0;
}
You can do something like:
Record::Record(std::string s){
std::string word;
std::vector<std::string> temp;
std::stringstream ss(s);
while(getline(ss, word, ','))
temp.push_back(word);
department = temp.at(0);
item_code = temp.at(1);
quantity = stoi(temp.at(2));
cost = stod(temp.at(3));
}
I'm assuming you mean that each parmeter is separated by ',', not each line, if that's not the case, say something.
Edit
So there are a couple of issues in your main function, namely, the while getline cycle will probably have out_of_range access, you can use a range-based for loop, which avoids container overflow:
while (getline(ifs, line))
{
bool flag = 1;
Record newRecord(line);
for(Record r : records){
if(r == newRecord){
flag = 0;
break;
}
}
if(flag)
records.push_back(newRecord);
}
The ofstream file is not being properly opened and tested, you can do something like:
ofstream ofs;
ofs.open(path + newFile);
if (!ofs)
{
cerr << "Can't open file " << filename << endl;
return 1;
}
for (size_t i = 0; i < records.size(); i++)
{
ofs << (i + 1) << ' ' << records[i] << endl;
}
This line:
path == "";
I'm sure you meant:
path = "";
Running sample
One last note, using namespace std is not a very good practice.
Related
I created this code to read and filter my csv files.
It works like I want it to work for small files.
But I just tried out a file of size 200k lines and it takes around 4 minutes, which is too long for my use case.
After testing a bit and fixing some quite stupid things I got the time down a little to 3 minutes.
I found out about half of the Time is spent reading in the file and half of the Time is spend generating the Result Vector.
Is there any way to Improve the speed of my Programm?
Especially the Reading from csv part?
I do not really have an Idea at the moment.
I'd appreciate any help.
EDIT:The filter is filtering the data by either a timeframe or timeframe and filterword in specific columns and outputting the data into a resulting vector of strings.
My CSV files look like this->
Headers are:
ID;Timestamp;ObjectID;UserID;Area;Description;Comment;Checksum
Data is:
523;19.05.2021 12:15;####;admin;global;Parameter changed to xxx; Comment;x3J2j4
std::ifstream input_file(strComplPath, std::ios::in);
int counter = 0;
while (std::getline(input_file, record))
{
istringstream line(record);
while (std::getline(line, record, delimiter))
{
record.erase(remove(record.begin(), record.end(), '\"'), record.end());
items.push_back(record);
//cout << record;
}
csv_contents[counter] = items;
items.clear();
++counter;
}
for (int i = 0; i < csv_contents.size(); i++) {
string regexline = csv_contents[i][1];
string endtime = time_upper_bound;
string starttime = time_lower_bound;
bool checkline = false;
bool isInRange = false, isLater = false, isEarlier = false;
// Check for faulty Data and replace it with an empty string
for (int oo = 0; oo < 8; oo++) {
if (csv_contents[i][oo].rfind("#", 0) == 0) {
csv_contents[i][oo] = "";
}
}
if ((regex_search(starttime, m, timestampformat) && regex_search(endtime, m, timestampformat))) {
filtertimeboth = true;
}
else if (regex_search(starttime, m, timestampformat)) {
filterfromstart = true;
}
else if (regex_search(endtime, m, timestampformat)) {
filtertoend = true;
}
}
I'm not sure exactly what the bottleneck is in your program (I copied your code from an earlier version of the question) but you have a lot of regex:es and mix reading records with post processing. I suggest that you create a class to hold one of these records, called record, overload operator>> for record and then use std::copy_if from the file with a filter that you can design separately from the reading. Do post processing after you've read the records that passes the filter.
I made a small test and it takes 2 seconds to read 200k records on my old spinning disk while doing filtering. I only used time_lower_bound and time_upper_bound to filter and additional checks will of course make it a little slower, but it should not take minutes.
Example:
#include <algorithm>
#include <chrono>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
// the suggested class to hold a record
struct record {
int ID;
std::chrono::system_clock::time_point Timestamp;
std::string ObjectID;
std::string UserID;
std::string Area;
std::string Description;
std::string Comment;
std::string Checksum;
};
// A free function to read a time_point from an `istream`:
std::chrono::system_clock::time_point to_tp(std::istream& is, const char* fmt) {
std::chrono::system_clock::time_point tp{};
// C++20:
// std::chrono::from_stream(is, tp, fmt, nullptr, nullptr);
// C++11 to C++17 version:
std::tm tmtp{};
tmtp.tm_isdst = -1;
if(is >> std::get_time(&tmtp, fmt)) {
tp = std::chrono::system_clock::from_time_t(std::mktime(&tmtp));
}
return tp;
}
// The operator>> overload to read one `record` from an `istream`:
std::istream& operator>>(std::istream& is, record& r) {
is >> r.ID;
r.Timestamp = to_tp(is, ";%d.%m.%Y %H:%M;"); // using the helper function above
std::getline(is, r.ObjectID, ';');
std::getline(is, r.UserID, ';');
std::getline(is, r.Area, ';');
std::getline(is, r.Description, ';');
std::getline(is, r.Comment, ';');
std::getline(is, r.Checksum);
return is;
}
// An operator<< overload to print one `record`:
std::ostream& operator<<(std::ostream& os, const record& r) {
std::ostringstream oss;
oss << r.ID;
{ // I only made a C++11 to C++17 version for this one:
std::time_t time = std::chrono::system_clock::to_time_t(r.Timestamp);
std::tm ts = *std::localtime(&time);
oss << ';' << ts.tm_mday << '.' << ts.tm_mon + 1 << '.'
<< ts.tm_year + 1900 << ' ' << ts.tm_hour << ':' << ts.tm_min << ';';
}
oss << r.ObjectID << ';' << r.UserID << ';' << r.Area << ';'
<< r.Description << ';' << r.Comment << ';' << r.Checksum << '\n';
return os << oss.str();
}
// The reading and filtering part of `main` would then look like this:
int main() { // not "void main()"
std::istringstream time_lower_bound_s("20.05.2019 16:40:00");
std::istringstream time_upper_bound_s("20.05.2021 09:40:00");
// Your time boundaries as `std::chrono::system_clock::time_point`s -
// again using the `to_tp` helper function:
auto time_lower_bound = to_tp(time_lower_bound_s, "%d.%m.%Y %H:%M:%S");
auto time_upper_bound = to_tp(time_upper_bound_s, "%d.%m.%Y %H:%M:%S");
// Verify that the boundaries were parsed ok:
if(time_lower_bound == std::chrono::system_clock::time_point{} ||
time_upper_bound == std::chrono::system_clock::time_point{}) {
std::cerr << "failed to parse boundaries\n";
return 1;
}
std::ifstream is("data"); // whatever your file is called
if(is) {
std::vector<record> recs; // a vector with all the records
// create your filter
auto filter = [&time_lower_bound, &time_upper_bound](const record& r) {
// Only copy those `record`s within the set boundaries.
// You can add additional conditions here too.
return r.Timestamp >= time_lower_bound &&
r.Timestamp <= time_upper_bound;
};
// Copy those records that pass the filter:
std::copy_if(std::istream_iterator<record>(is),
std::istream_iterator<record>{}, std::back_inserter(recs),
filter);
// .. post process `recs` here ...
// print result
for(auto& r : recs) std::cout << r;
}
}
Answer is already given by Ted. I made a solution in the same time. So let me show it additionally.
I created test data with 500k records and all parsing an stuff was done in below 3 seconds on my machine.
Additionally, I also created classes.
Speed will be gained by using std::move, increasing the input buffer size and using reservefor the std::vector.
Please see yet another solution below. I omitted filtering. Ted showed it already.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <ctime>
#include <vector>
#include <chrono>
#include <sstream>
#include <algorithm>
#include <iterator>
constexpr size_t MaxLines = 600'000u;
constexpr size_t NumberOfLines = 500'000u;
const std::string fileName{ "test.csv" };
// Dummy rtoutine for writing a test file
void createFile() {
if (std::ofstream ofs{ fileName }; ofs) {
std::time_t ttt = 0;
for (size_t k = 0; k < NumberOfLines; ++k) {
std::time_t time = static_cast<time_t>(ttt);
ttt += 1000;
ofs << k << ';'
#pragma warning(suppress : 4996)
<< std::put_time(std::localtime(&time), "%d.%m.%Y %H:%M") << ';'
<< k << ';'
<< "UserID" << k << ';'
<< "Area" << k << ';'
<< "Description" << k << ';'
<< "Comment" << k << ';'
<< "Checksum" << k << '\n';
}
}
else std::cerr << "\n*** Error: Could not open '" << fileName << "' for writing\n\n";
}
// We will create a bigger input buffer for our stream
constexpr size_t ifStreamBufferSize = 100'000u;
static char buffer[ifStreamBufferSize];
// Object oriented Model. Class for one record
struct Record {
// Data
long id{};
std::tm time{};
long objectId{};
std::string userId{};
std::string area{};
std::string description{};
std::string comment{};
std::string checkSum{};
// Methods
// Extractor operator
friend std::istream& operator >> (std::istream& is, Record& r) {
// Read one complete line
if (std::string line; std::getline(is, line)) {
// Here we will stor the parts of the line after the split
std::vector<std::string> parts{};
// Convert line to istringstream for further extraction of line parts
std::istringstream iss{ line };
// One part of a line
std::string part{};
bool wrongData = false;
// Split
while (std::getline(iss, part, ';')) {
// Check fpor error
if (part[0] == '#') {
is.setstate(std::ios::failbit);
break;
}
// add part
parts.push_back(std::move(part));
}
// If all was OK
if (is) {
// If we have enough parts
if (parts.size() == 8) {
// Convert parts to target data in record
r.id = std::strtol(parts[0].c_str(), nullptr, 10);
std::istringstream ss{parts[1]};
ss >> std::get_time(& r.time, "%d.%m.%Y %H:%M");
if (ss.fail())
is.setstate(std::ios::failbit);
r.objectId = std::strtol(parts[2].c_str(), nullptr, 10);
r.userId = std::move(parts[3]);
r.area = std::move(parts[4]);
r.description = std::move(parts[5]);
r.comment = std::move(parts[6]);
r.checkSum = std::move(parts[7]);
}
else is.setstate(std::ios::failbit);
}
}
return is;
}
// Simple inserter function
friend std::ostream& operator << (std::ostream& os, const Record& r) {
return os << r.id << " "
#pragma warning(suppress : 4996)
<< std::put_time(&r.time, "%d.%m.%Y %H:%M") << " "
<< r.objectId << " " << r.userId << " " << r.area << " " << r.description << " " << r.comment << " " << r.checkSum;
}
};
// Data will hold all records
struct Data {
// Data part
std::vector<Record> records{};
// Constructor will reserve space to avaoid reallocation
Data() { records.reserve(MaxLines); }
// Simple extractor. Will call Record's exractor
friend std::istream& operator >> (std::istream& is, Data& d) {
// Set bigger file buffer. This is a time saver
is.rdbuf()->pubsetbuf(buffer, ifStreamBufferSize);
std::copy(std::istream_iterator<Record>(is), {}, std::back_inserter(d.records));
return is;
}
// Simple inserter
friend std::ostream& operator >> (std::ostream& os, const Data& d) {
std::copy(d.records.begin(), d.records.end(), std::ostream_iterator<Record>(os, "\n"));
return os;
}
};
int main() {
// createFile();
auto start = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
if (std::ifstream ifs{ fileName }; ifs) {
Data data;
// Start time measurement
start = std::chrono::system_clock::now();
// Read and parse complete data
ifs >> data;
// End of time measurement
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
std::cout << "\nReading and splitting. Duration: " << elapsed.count() << " ms\n";
// Some debug output
std::cout << "\n\nNumber of read records: " << data.records.size() << "\n\n";
for (size_t k{}; k < 10; ++k)
std::cout << data.records[k] << '\n';
}
else std::cerr << "\n*** Error: Could not open '" << fileName << "' for reading\n\n";
}
And yes, I used "ctime".
I'm trying to write a program that prompts a user for a file and its file path and then reads the file. It then takes the text in the file and stores it into a vector after parsing the data and separating it into a few categories (department, item code, quantity, and cost).
I'm having a few issues knowing how to use getline() correctly, and I'm unsure of a few aspects of it. Overall I'm trying to understand how to assign each element to the appropriate object variable, currently I'm lacking in experience with doing this sort of thing as well as using OOP.
The text contained in the file will look like the following.
21 Music 64679-701 487 28.77
22 Outdoors 63739-141 195 83.23
23 Books 0268-1154 976 65.17
After I am able to get that portion working correctly I'll be working on checking for duplicates and ignoring any that are found.
main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <libgen.h>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include "Record.h"
using namespace std;
int main() {
vector<Record> records; //vector of type Records to hold each "Line" of input file
string filename; // File name and path stored as string
/**
* Prompts user for the name of input file and stores in string variable filename
*
*/
cout << "please enter the name of your file with file path:" << endl;
cin >> filename;
ifstream ifs { filename.c_str() };
if (!ifs) {
cerr << " Can't open file " << filename << endl;
return 1;
}
string path = filename.substr(0, filename.find_last_of("\\/"));
string file = filename.substr(filename.find_last_of("\\/") + 1,
filename.length());
if (path.compare(file) == 0) {
path == "";
}
//test for file and file path
cout << "Path portion of " << filename << " is " << path << endl; //
cout << "File portion of " << filename << " is " << file << endl; // path + "new_" + file + ".cvs", make new file with new path
/**
* Put each line of input file in to the records vector
*/
string line; //strings for each parameter of the vector object
while (getline(ifs, line)) {
Record newRecord(line);
//Here is where I'm having trouble with using get line to parse and store the information.
//It is incorrect at the moment, and I have the start of how I think I should be going about it.
// check if this record exists in the vector, if not add, else ignore
records.push_back(newRecord);
}
ifs.close(); //closes the stream
return 0;
}
Record.h
#ifndef RECORD_H_
#define RECORD_H_
#include <iostream>
#include <string>
class Record {
public:
//Constructor
Record(std::string s); //pass this string to our Record class
//De-constructor
virtual ~Record();
//overloaded "==" and "<" comparison operators
friend bool operator ==(const Record &a, const Record &b);
//friend bool operator <(const Record &a, const Record &b); //Do not need at this time.
//Overloaded "<<" operator
friend std::ostream& operator <<(std::ostream&, const Record&);
private:
std::string department;
std::string item_code;
int quantity;
double cost;
};
#endif /* RECORD_H_ */
Record.cpp
#include <string>
#include "Record.h"
using namespace std;
Record::Record(string s) {
/**
* Create a string stream from 's' and use getline(stringStream, line, ",") to
* read each element from the string using the "," as the delimiter. Assign
* each element to the appropriate object variable
*/
//getline(s, line.cost, line.department, line.item_code, line.quantity, ",");
}
Record::~Record() {
// TODO Auto-generated destructor stub
}
//overloaded "==" and "<" comparison operators
bool operator ==(const Record &lhs, const Record &rhs){
return (lhs.cost == rhs.cost && lhs.department == rhs.department &&
lhs.item_code == rhs.item_code && lhs.quantity == rhs.quantity);
}
/**bool operator <(const Record &a, const Record &b){ //do not need at this time
}
**/
//Overloaded "<<" operator
std::ostream& operator <<(std::ostream& os, const Record& r){
os << r.department << ',' << r.item_code << ',' << r.quantity << ',' << r.cost;
return os;
}
123 Michael
456 Calimlim
898 Mykfyy
999 Kyxy
657 mykfyy
898 Help
I'm creating a student attendance keeper system. One of the features of my system is that the student need to register(his/her id and name) first to access the system (login with his/her id)
The problem is that i don't know and i don't want my student to have a similar ID number Like(e.g 898 Mykfyy and 898 Help)
I'm using fstream in my system. I've been thinking that if I want to avoid the duplication i need to Read(ifstream) the .txt file before register(oustream). But i dont know how to read line by line and check if the ID(898) is already use/existed
In C++ one wouldn't deal with lines, but with objects:
#include <limits>
#include <cstdlib>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <iterator>
#include <algorithm>
struct student_t
{
unsigned id;
std::string name;
};
bool operator==(student_t const &lhs, student_t const &rhs)
{
return lhs.id == rhs.id;
}
std::ostream& operator<<(std::ostream &os, student_t const &student)
{
return os << student.id << ' ' << student.name;
}
std::istream& operator>>(std::istream &is, student_t &student)
{
unsigned id;
if (!(is >> id))
return is;
std::string name;
if (!std::getline(is, name)) {
return is;
}
student = student_t{ id, name };
return is;
}
int main()
{
char const *filename{ "test.txt" };
std::ifstream input{ filename };
if (!input.is_open()) {
std::cerr << "Couldn't open \"" << filename << "\" for reading :(\n\n";
return EXIT_FAILURE;
}
std::vector<student_t> students{ std::istream_iterator<student_t>{ input }, std::istream_iterator<student_t>{} };
input.close();
std::copy(students.begin(), students.end(), std::ostream_iterator<student_t>{ std::cout, "\n" });
student_t new_student;
while (std::cout << "New Student?\n", !(std::cin >> new_student)) {
std::cerr << "Input error :(\n\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
auto it{ std::find(students.begin(), students.end(), new_student) };
if (it != students.end()) {
std::cerr << "Sorry, but a student with id " << new_student.id << " already exists :(\n\n";
return EXIT_FAILURE;
}
std::ofstream output{ filename, std::ios::app };
if (!output.is_open()) {
std::cerr << "Couldn't open \"" << filename << "\" for writing :(\n\n";
return EXIT_FAILURE;
}
output << new_student << '\n';
std::cout << "New student [" << new_student << "] added :)\n\n";
}
The easiest way to do it is probably using std::getline to get the current line as a string:
using namespace std;
ifstream in(fileName);
string line;
while(getline(in, line))
{
// --do something with the line--
}
You will then need to parse each line to get the correct ID
Edit: updated to remove eof()
Depends on how you implemented it.
I suppose the number of people isn't too large so I would just check the Id's before a new ID is added.
Something like
If ID exists, not add.
I am trying to learn how to use classes and I figured I'd create some sort of supermarket system to aid me with learning. After I have saved all the values from my text file into the temp variables, how do I then use them to create an object? I assume I want one object per item you can "buy"?
If you have any other tips on how to improve my code, please mention them as I just started with C++ a few days ago.
My text file looks like:
42 68 Apples
35 1 Oranges
70 25 Bananas
And my code is below:
// Classes.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
class Products {
private:
int price;
int ID;
int quantity;
public:
void setPrice(int newPrice) {
price = newPrice;
}
void setID(int newID) {
ID = newID;
}
void setQuantity(int newQuantity) {
quantity = newQuantity;
}
int getPrice() {
return price;
}
int getID() {
return ID;
}
int getQuantity() {
return quantity;
}
};
int main()
{
std::string line;
std::string input;
std::string temp;
std::string temp2;
std::string temp3;
int counter = 0;
while (input != "e" && input != "r") {
std::cout << "Do you want to (r)ead the inventory or (e)dit it? (input 'r' or 'e'): ";
getline(std::cin, input);
}
if (input == "r") {
std::ifstream pFile ("products.txt");
if (pFile.is_open()) {
while (getline(pFile, line)) {
std::istringstream iss(line);
iss >> temp >> temp2 >> temp3;
counter++;
}
}
}
return 0;
}
Looking at the text file that you are using as an example and trying to populate a set of classes with the information I'd do something like this:
Text File
42 68 Apples
35 1 Oranges
70 25 Bananas
Looking at the text file you have an int followed by space then another int followed by another space finally followed by a varying size of char[]. We can use this information to create your class or struct and this is how I would create the class based off of the content from the file that is being read in or parsed.
Produce.h
#ifndef PRODUCE_H
#define PRODUCE_H
#include <string>
class Produce {
private:
unsigned price_; // Normally would use float but will use unsigned for simplicity
unsigned quantity_;
std::string name_; // You could use the int as an id as you were using
public:
Produce() {} // Default Constructor
~Produce() {} // Default Destructor
// User Constructor
Produce(const std::string& name, const unsigned& price, const unsigned& qty )
: name_(name), price_(price), quantity_( qty ) {}
// Copy By Const Reference Constructor
Produce(const Produce& other) {
name_ = other.name_;
price_ = other.price_;
quantity_ = other.quantity_;
}
// Assignment Operator
Produce& operator=(const Produce& other) {
name_ = other.name_;
price_ = other.price_;
quantity_ = other.quantity_;
return *this;
}
// Setters & Getters
void setOrChangePrice(const unsigned& priceChange) {
price_ = priceChange;
}
unsigned getPrice() const { return price_; }
void setName(const std::string& name) {
// Already Has A Name? Return!
if (!name_.empty())
return;
}
std::string getName() const { return name_; }
void setOrChangeQuantity(const unsigned& qty) {
quantity_ = qty;
}
unsigned getQuantity() const {
return quantity_;
}
};
#endif // PRODUCE_H
Produce.cpp
#include "Produce.h"
// Normally Constructor(s) & Function Definitions Would Go Here
// Since this is a simple class; I declared & defined them in the header.
Inventory.h
#ifndef INVENTORY_H
#define INVENTORY_H
#include <vector>
#include "Produce.h"
class Inventory {
private:
std::vector<Produce> produce_;
public:
Inventory() {}
~Inventory() {
if (!produce_.empty()) {
produce_.clear();
}
}
std::vector<Produce> getProduceList() const {
return produce_;
}
unsigned getProduceListSize() const {
return static_cast<unsigned>( produce_.size() );
}
void addProduce(const Produce& produce) {
produce_.push_back(produce);
}
Produce getProduce(const std::string& name) const {
for (unsigned n = 0; n < produce_.size(); ++n) {
if (name == produce_[n].getName()) {
return produce_[n];
} else {
return Produce();
}
}
}
unsigned getPrice(const std::string& name) const {
for (unsigned n = 0; n < produce_.size(); ++n) {
if (name == produce_[n].getName()) {
return produce_[n].getPrice();
} else {
return 0;
}
}
}
unsigned getQuantity( const std::string& name ) const {
for (unsigned n = 0; n < produce_.size(); ++n) {
if (name == produce_[n].getName()) {
return produce_[n].getQuantity();
} else {
return 0;
}
}
}
};
#endif // INVENTORY_H
Inventory.cpp
#include "Inventory.h"
Main.cpp
// #include <vector> // Also Included In Inventory.h
// #include <string> // Also Included In Produce.h
#include <iostream>
#include <fstream>
// #include "Produce.h" // Also Included In Inventory.h
#include "Inventory.h"
int main( ) {
// Same As Your Text File
std::string strFilename("ProduceInventory.txt");
std::ifstream fileIn;
// Temps
unsigned price = 0;
unsigned qty = 0;
std::string name;
Inventory inventory;
fileIn.open(strFilename.c_str());
if (!fileIn.is_open()) {
std::cout << "Can not read file\n";
}
while (fileIn >> price >> qty >> name) {
Produce produce(name, price, qty);
inventory.addProduce(produce);
}
if ( fileIn.is_open() ) {
fileIn.close();
}
// Test Our Inventory From File
for (unsigned n = 0; n < inventory.getProduceListSize(); n++) {
std::cout << "Name: " << inventory.getProduceList()[n].getName() << " "
<< "Price: " << inventory.getProduceList()[n].getPrice() << " "
<< "Qty: " << inventory.getProduceList()[n].getQuantity() << "\n";
}
std::cout << std::endl;
// Check To See If Our Search Method Works
std::cout << "Enter a product type by name to get its price and qty on hand.\n";
name.clear(); // reuse
std::cin >> name;
Produce p = inventory.getProduce(name);
if (p.getName().empty()) {
std::cout << "We either do not carry this produce or we are out of stock\n";
} else {
std::cout << "Our price is " << p.getPrice() << " and we have " << p.getQuantity() << " on hand\n";
// Or
std::cout << "Our price is " << inventory.getPrice(name) << " and we have " << inventory.getQuantity(name) << " on hand\n";
}
return 0;
}
Now this is just one of many ways this can be done; but also make sure that your text file is in the appropriate location of the working directory for your IDE or that you specify the correct path along with the filename and extension to be able to open and read from the file in the first place.
Much of this code can be fined even more; but I was showing how to use the std::vector<T> container and some of its functions and how to iterate through them and to retrieve its data. This is a basic way of parsing a simple text file when you know the exact format of the text file and for each and every line you will have the same data types in the same order.
This will not work for every kind of file parsing because it depends on the file's format. For example another file might have different kind of data from one line to the next and might have keywords or tags to give a description of what kind of data is coming next. For that kind of parsing you would have to read the entire line in first into a string stream then you would have to be able to parse that string stream via tokens to extract the data. Some times parsing a file might have multiple lines that belong to a data set and for that you would have to parse by chunks or blobs.
Parsing text files is harder to parse than binary file formats because you have to check each line and character of the text and you also have to be cautious
of character returns, new line characters etc. With binary format you just have to know how many bytes to read in and what type of data it is expected to be. There are many books that are written on just this topic alone on how to parse data from a file. There is no one simple way that fits all.
I have used the following piece of code to read from multiple .dat files and parse them. This code uses 3D vectors to store data after the reading process. However, I would like that the data corresponding to each single file be independent from the others. The issue is that the number of files varies, and is unknown at compile time; hence, the number of vectors varies too. I would like to know if there is any solution for this.
vector<vector<vector<string>>> masterList;
for (int i = 0; i < files.size(); ++i) {
cout << "file name: " << files[i] << endl;
fin.open(files[i].c_str());
if (!fin.is_open()) {
// error occurs!!
// break or exit according to your needs
cout<<"error"<<endl;
}
std::vector<vector<string>> tokens;
int current_line = 0;
std::string line;
while (std::getline(fin, line))
{
cout<<"line number: "<<current_line<<endl;
// Create an empty vector for this line
tokens.push_back(vector<string>());
//copy line into is
std::istringstream is(line);
std::string token;
int n = 0;
//parsing
while (getline(is, token, DELIMITER))
{
tokens[current_line].push_back(token);
cout<<"token["<<current_line<<"]["<<n<<"] = " << token <<endl;
n++;
}
cout<<"\n";
current_line++;
}
fin.clear();
fin.close();
masterList.push_back(tokens);
}
So, the main issue I'm facing is: how to create a variable number of 2D vectors to store the data corresponding to each single file, when I don't know how many files there are at compile time.
Modify the list of files in the main to adapt the size of your "master data". If the length of file names is variable, then parse it first (or get it one way or another first), and then execute the parsing on the dat files. If the filenames are known at run time only, and asynchronously with that, then add a new element in the list each time you get a new filename (you can use events for that for example, take a look at https://github.com/Sheljohn/siglot).
Note that list elements are independent in memory, and that lists support deletion/insertion in constant time. That way, data corresponding to each file is independent from the other. If you want to retrieve the data specific to a file (knowing the filename), either iterate on the list to find the corresponding file (linear time) or trade the list for an unordered_map (amortized constant time).
#include <string>
#include <list>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iterator>
#include <algorithm>
using namespace std;
#define AVG_LINES_PER_FILE 100
/**
* [tokenize_string Tokenize input string 'words' and put elements in vector 'tokens'.]
* #param words [Space separated data-string.]
* #param tokens [Vector of strings.]
*/
void tokenize_string( string& words, vector<string>& tokens )
{
unsigned n = count( words.begin(), words.end(), ' ' );
tokens.reserve(n);
istringstream iss(words);
copy(
istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter<vector<string> >(tokens)
);
}
/**
* Contains data parsed from a single .dat file
*/
class DATFileData
{
public:
typedef vector<string> line_type;
typedef vector<line_type> data_type;
DATFileData( const char* fname = nullptr )
{
m_fdata.reserve(AVG_LINES_PER_FILE);
m_fdata.clear();
if ( fname ) parse_file(fname);
}
// Check if the object contains data
inline operator bool() const { return m_fdata.size(); }
// Parse file
bool parse_file( const char* fname )
{
string line;
m_fdata.clear();
ifstream fin( fname );
if ( fin.is_open() )
{
while ( fin.good() )
{
getline(fin,line);
m_fdata.push_back(line_type());
tokenize_string( line, m_fdata.back() );
}
fin.close();
m_fname = fname;
cout << "Parsed " << m_fdata.size() << " lines in file '" << fname << "'." << endl;
return true;
}
else
{
cerr << "Could not parse file '" << fname << "'!" << endl;
return false;
}
}
// Get data
inline unsigned size() const { return m_fdata.size(); }
inline const char* filename() const { return m_fname.empty() ? nullptr : m_fname.c_str(); }
inline const data_type& data() const { return m_fdata; }
inline const line_type& line( const unsigned& i ) const { return m_fdata.at(i); }
private:
string m_fname;
data_type m_fdata;
};
int main()
{
unsigned fcount = 0;
vector<string> files = {"some/file/path.dat","another/one.dat"};
list<DATFileData> data(files.size());
for ( DATFileData& d: data )
d.parse_file( files[fcount++].c_str() );
cout << endl << files.size() << " files parsed successfully." << endl;
}