C++ templates containing std::vector - c++

Very recently I wrote a class containing four functions to open and read multicolumn data files (up to 4 columns). In the function the name of the file to be opened "file_name" is passed from the main program to the function in class "Read_Columnar_File". The data is read in using std::vector and passed back to the main program. However, it required that the programmer change the data types of the input columns every time it is used which is a recipe for errors. The file name will always be a character string, so that does not need to be templated; however, the data type of the arrays read in using vector can change, so that needs to be generically templated. I am trying to convert the class to a template class and am missing some fundamental understanding of the process with respect to making templates containing std::vector. To simplify the development process I have gone back to a single routine titled "Read_One_Column" inside the class and am trying to convert it a template where the data type is labeled as Type1. I think my problem is in the syntax since the debugger is telling me that the command in the main program is undefined. Any advice to help correct this would be appreciated. A copy of the existing code is attached below.
#include <vector>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <iterator>
template <class Type1> class Read_Columnar_File {
public:
void Read_One_Column(const std::string& file_name,std::vector<Type1>& Column1);
};
template <class Type1> void Read_Columnar_File<Type1>::Read_One_Column(const std::string& file_name,
std::vector<Type1>& Column1)
{
std::ifstream inp(file_name,std::ios::in | std::ios::binary);
std::istream_iterator<Type1> start((inp)), end;
if(inp.is_open()) {
Column1.assign(start,end);
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}
int main(int argc, const char * argv[]) {
int i;
std::vector<float> str1;
Read_Columnar_File<float> ob1;
char str[20];
std::strcpy(str,"Test.txt");
ob1.Read_One_Column(str,str1);
for(i=0; i < 7; i++) std::cout << str1[i] << std::endl;
return 0;
}

The syntax is simpler:
template <typename Type1>
void Read_One_Column(const std::string& file_name,
std::vector<Type1>& Column1) {
...
}
no need to create a class at all (it's just a template function).
If you need to put the function in a class for other reasons then the syntax is the same
struct Read_Columnar_File {
...
template<typename Type1>
void Read_One_Column(const std::string& file_name,
std::vector<Type1>& Column1) {
...
}
}
and it will be a template method of the class.

To fully close out this question I am posting the final and correct code since I am sure others will have the same question in the future and i hope this helps them. To answer my question, when programming a template, the entire algorithm needs to be included in the header and cannot be split between a header and implementation file. This program is meant to be a very generic method of reading in columnar data from an input file and assumes that each column of data is the same length as the others. The user can merely like the header file to their main program, specify the data type of each column in the vector definition and read in the data. The main program is shown below. This version allows the user to call 4 different functions which can be used to read in up to as much as four columns of data.
#include <vector>
#include <iostream>
#include <cstring>
#include "Read_Columnar_File.h"
int main(int argc, const char * argv[]) {
char str[20];
strcpy(str,"Test.txt");
// - Format for reading in a single column of data
// Data in this case is declared as a float in
// the vector, but it can be any data type
/*
std::vector<float> str2;
Read_One_Column(str,str2);
*/
// - Format for reading in two columns of data from
// an input file
/*
std::vector<float> str2;
std::vector<int> str3;
Read_Two_Columns(str,str2,str3);
*/
// - Format for reading in three columns of data from
// an input file
/*
std::vector<float> str2;
std::vector<int> str3;
std::vector<int> str4;
Read_Three_Columns(str,str2,str3,str4);
*/
std::vector<float> str2;
std::vector<int> str3;
std::vector<int> str4;
std::vector<float> str5;
Read_Four_Columns(str,str2,str3,str4,str5);
return 0;
}
The implementation file is shown below.
#include <vector>
#include <stdio.h>
#include <fstream>
#include <iterator>
template <class X> void Read_One_Column(const std::string& file_name,std::vector<X>& Column1)
{
std::ifstream inp(file_name,std::ios::in | std::ios::binary);
std::istream_iterator<X> start((inp)), end;
if(inp.is_open()) {
Column1.assign(start,end);
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}
template <class X,class Y> void Read_Two_Columns(const std::string& file_name,std::vector<X>& Column1,
std::vector<Y>& Column2)
{
int i;
X Col1;
Y Col2;
std::ifstream inp(file_name,std::ios::in | std::ios::binary);
if(inp.is_open()){
for(i=0; i < 7; i++){
inp >> Col1 >> Col2;
Column1.push_back(Col1), Column2.push_back(Col2);
}
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}
template <class X,class Y, class Z> void Read_Three_Columns(const std::string& file_name,std::vector<X>& Column1,
std::vector<Y>& Column2,std::vector<Z>& Column3
{
int i;
X Col1;
Y Col2;
Z Col3;
std::ifstream inp(file_name,std::ios::in | std::ios::binary);
if(inp.is_open()){
for(i=0; i < 7; i++){
inp >> Col1 >> Col2 >> Col3;
Column1.push_back(Col1), Column2.push_back(Col2), Column3.push_back(Col3);
}
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}
template <class X,class Y, class Z,class A> void Read_Four_Columns(const std::string& file_name,std::vector<X>& Column1,
std::vector<Y>& Column2,std::vector<Z>& Column3,
std::vector<A>& Column4)
{
int i;
X Col1;
Y Col2;
Z Col3;
A Col4;
std::ifstream inp(file_name,std::ios::in | std::ios::binary);
if(inp.is_open()){
for(i=0; i < 7; i++){
inp >> Col1 >> Col2 >> Col3 >> Col4;
Column1.push_back(Col1), Column2.push_back(Col2),
Column3.push_back(Col3), Column4.push_back(Col4);
}
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}

Related

How to use pointer with struct to refer to the field of each struct

Below code is the normal way to get the input from a text and store it in an array in a structure.
Wanted to ask how can i use pointer to store all these data into the array of structure ? Like p1->Years (this is without array, but how can i apply this to way of writing in below code)
Any better suggestion to use pointer to take in the input?
int years = 4;
struct maju_company {
int Year;
float quarter1, quarter2, quarter3, quarter4, total_sales, average_sales;
};
int main() {
string line;
maju_company p1[years];
fstream yeecinnfile("MajuSales.txt");
if(yeecinnfile.is_open()) {
//ignoring the first four line of code and store the rest of the code
string line1,line2,line3,line4;
getline(yeecinnfile,line1);
getline(yeecinnfile,line2);
getline(yeecinnfile,line3);
getline(yeecinnfile,line4);
while(!yeecinnfile.eof()) {
for(int i =0; i<years; i++) {
yeecinnfile>>p1[i].Year>>p1[i].quarter1>>p1[i].quarter2>>p1[i].quarter3>>p1[i].quarter4;
}
}
for(int i =0; i<years; i++) {
cout<<p1[i].Year<<setw(10)<<p1[i].quarter1<<setw(10)<<p1[i].quarter2<<setw(10)<<p1[i].quarter3<<setw(10)<<p1[i].quarter4<<endl;
}
cout<<endl;
}
}
I see nothing wrong with the way you do this.
However, you could create a pointer to each record inside the loop
maju_company* p = &p1[i];
and then use p-> instead of p1[i]., but I really don't see this as an improvement.
If the reading loop looks too complicated, I would rather move the code to a separate function, perhaps
void read_record(maju_company& company);
or maybe
maju_company read_record();
and then only have to handle a single company inside the function (so no indexing and no ponters there).
I think you wouldn't need pointers at all for your example.
Use a std::vector to hold all your data and then there are other
things from C++ I think you should learn to use, example here :
(if you have questions let me know)
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
// dont use : using namespace std;
struct maju_company_data
{
int year;
float quarter1, quarter2, quarter3, quarter4, total_sales, average_sales;
};
// describe how to stream data to an output stream (like std::cout)
std::ostream& operator<<(std::ostream& os, const maju_company_data& data)
{
os << "-----------------------------------------------------\n";
os << "Company data for year : " << data.year << "\n";
os << "Quarter 1 : " << data.quarter1 << "\n";
os << "Quarter 2 : " << data.quarter1 << "\n";
os << "Quarter 3 : " << data.quarter1 << "\n";
os << "Quarter 4 : " << data.quarter1 << "\n";
os << "\n";
return os;
}
int main()
{
// no need to manage pointers yourself use a vector
std::vector<maju_company_data> company_yearly_data; // give variables a meaningful name
std::ifstream ifile("MajuSales.txt"); // ifstream your using file as input
std::string line1, line2, line3, line4;
// ignore first line
ifile >> line1;
while (ifile >> line1 >> line2 >> line3 >> line4) // probably you need to read a few more lines here
{
maju_company_data data;
// convert read strings to numbers
data.year = std::stoi(line1);
data.quarter1 = std::stof(line2);
data.quarter2 = std::stof(line3);
data.quarter3 = std::stof(line4);
//..
//data.quarter4 = std::stof(line5);
//data.total_sales = std::stof(line6);
company_yearly_data.push_back(data);
};
// this is a range based for loop
// it is prefered since you cant go out of bounds
// const auto& means that data will be an unmodifiable
// reference to each of the structs stored in the vector
for (const auto& data : company_yearly_data)
{
std::cout << data; // since we overloaded << this loop will be nice and clean
}
return 0;
}
A C++ approach to this to overload the istream operator>> and ostream operator<< for your specific type. E.g.
#include <algorithm>
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
static constexpr auto years{4};
struct maju_company {
int Year{};
float quarter1{}, quarter2{}, quarter3{}, quarter4{};
float total_sales{}, average_sales{}; // ALWAYS init your floats.
};
auto& operator>>(std::istream& is, maju_company& mc) {
is >> mc.Year
>> mc.quarter1 >> mc.quarter2 >> mc.quarter3 >> mc.quarter4
>> mc.total_sales >> mc.average_sales;
return is;
}
auto& operator<<(std::ostream& os, maju_company const& mc) {
os << mc.Year
<< std::setw(10) << mc.quarter1
<< std::setw(10) << mc.quarter2
<< std::setw(10) << mc.quarter3
<< std::setw(10) << mc.quarter4;
return os;
}
You can then go on to use the type using the std library, e.g.
int main() {
auto p1{std::array<maju_company, years>{}};
{
auto fs{std::fstream("MajuSales.txt")};
if (!fs.is_open()) return -1;
{
// throw away 4 lines
auto dummy{std::string{}};
for (auto i{0}; i < 4; ++i) getline(fs, dummy);
}
std::copy_n(std::istream_iterator<maju_company>{fs},
years,
begin(p1));
}
std::copy(cbegin(p1), cend(p1),
std::ostream_iterator<maju_company>{std::cout, "\n"});
}

Store input values into array while reading them in, c++

I am pretty new to c++. I am trying to read a file in line by line and store the input into several arrays.
Because I don't know the size of input file, I have this to get the number of lines in the file
while (std::getline(inputFile, line)){
++numOfLines;
std::cout << line << std::endl;
}
Now I want to use the numOfLines as the size of arrays, but i cannot get it run by having this
std::string *firstName= new std::string[numOfLines];
std::string *lastName= new std::string[numOfLines];
for (int i = 0; i < numOfLines; ++i)
{
line >> firstName[i];
}
I guess it is because it has reached the end of the file after the while loop. But I do not know how to solve this problem. Is there a way to scan the input file in and store the value into array at the same time?
If you use std::vector you don't need to know ahead the lines count. You can use vector method push_back to insert new elements into it. Try use something like this:
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
std::vector<std::string> first_names;
std::string line;
ifstream input_file;
while (std::getline(input_file, line)){
first_names.push_back(line);
}
for (size_t i = 0; i < first_names.size(); i++) {
std::cout << first_names[i] << std::endl;
}
return 0;
}
I don't know if you have ever taken a course related to Data Structures & Algorithms,
in which you will learn to use Containers (such as:
vector,
deque,
list, etc.) instead of Primitive Data Structures.
Please notice that although the follow example chooses vector as its container, it could vary according to different contexts. Say you are handling gigantic mount of data, you might want to use list instead`1,2,3.
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
// alias long type
// #see: https://en.cppreference.com/w/cpp/language/type_alias
using NameVector = std::vector<std::string>;
int handleLine(std::string line, NameVector &firstNames)
{
// TODO implement your line handler here
firstNames.push_back(line);
return 0;
}
int handleFile(std::ifstream inputFile, NameVector &firstNames)
{
std::string line;
for (int lineNum = 1;
// invoke `good` to check if there is any error
inputFile.good()
&&
std::getline(inputFile, line);
lineNum++)
{
std::cout << "Current line number : (" << lineNum << ")" << std::endl;
std::cout << "Current line content: (" << line << ")" << std::endl;
handleLine(line, &firstNames);
}
return 0;
}
int main()
{
std::string path; // = R"(HERE GOES YOUR FILE PATH)";
// Using **Raw string**
std::ifstream inputFile { path }; // Initialize `inputFile`
NameVector firstNames;
handleFile(inputFile, firstNames);
for (auto firstName : firstNames)
{
std::cout << firstName << std::endl;
}
return 0;
}

Read and print a csv file with more than 2 column in c++ using multimap

I'm a beginner in c++ and required to write a c++ program to read and print a csv file like this.
DateTime,value1,value2
12/07/16 13:00,3.60,50000
14/07/16 20:00,4.55,3000
May I know how can I proceed with the programming?
I manage to get the date only via a simple multimap code.
I spent some time to make almost (read notice at the end) exact solution for you.
I assume that your program is a console application that receives the original csv-file name as a command line argument.
So see the following code and make required changes if you like:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
std::vector<std::string> getLineFromCSV(std::istream& str, std::map<int, int>& widthMap)
{
std::vector<std::string> result;
std::string line;
std::getline(str, line);
std::stringstream lineStream(line);
std::string cell;
int cellCnt = 0;
while (std::getline(lineStream, cell, ','))
{
result.push_back(cell);
int width = cell.length();
if (width > widthMap[cellCnt])
widthMap[cellCnt] = width;
cellCnt++;
}
return result;
}
int main(int argc, char * argv[])
{
std::vector<std::vector<std::string>> result; // table with data
std::map<int, int> columnWidths; // map to store maximum length (value) of a string in the column (key)
std::ifstream inpfile;
// check file name in the argv[1]
if (argc > 1)
{
inpfile.open(argv[1]);
if (!inpfile.is_open())
{
std::cout << "File " << argv[1] << " cannot be read!" << std::endl;
return 1;
}
}
else
{
std::cout << "Run progran as: " << argv[0] << " input_file.csv" << std::endl;
return 2;
}
// read from file stream line by line
while (inpfile.good())
{
result.push_back(getLineFromCSV(inpfile, columnWidths));
}
// close the file
inpfile.close();
// output the results
std::cout << "Content of the file:" << std::endl;
for (std::vector<std::vector<std::string>>::iterator i = result.begin(); i != result.end(); i++)
{
int rawLen = i->size();
for (int j = 0; j < rawLen; j++)
{
std::cout.width(columnWidths[j]);
std::cout << (*i)[j] << " | ";
}
std::cout << std::endl;
}
return 0;
}
NOTE: Your task is just to replace a vector of vectors (type std::vector<std::vector<std::string>> that are used for result) to a multimap (I hope you understand what should be a key in your solution)
Of course, there are lots of possible solutions for that task (if you open this question and look through the answers you will understand this).
First of all, I propose to consider the following example and to try make your task in the simplest way:
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
using namespace std;
int main()
{
string str = "12/07/16 13:00,3.60,50000";
stringstream ss(str);
vector<string> singleRow;
char ch;
string s = "";
while (ss >> ch)
{
s += ch;
if (ss.peek() == ',' || ss.peek() == EOF )
{
ss.ignore();
singleRow.push_back(s);
s.clear();
}
}
for (vector<string>::iterator i = singleRow.begin(); i != singleRow.end(); i++)
cout << *i << endl;
return 0;
}
I think it can be useful for you.

std::vector passed by reference is not passing from a function to main()

This question is very similar to some other questions that have been posted on here, but nonetheless, it does not seem to work when I implement previously suggested solutions. I am writing what should be some simple software that can be used to read in one column or multiple columns of data from a .txt file in a function and pass it to the main program for further calculations. The function call passes the file name to the function and reads in the data. Since this version only reads in one columns of data I could use a return function for this specific example, but I plan on expanding this to read in multiple columns of data so that is why I am using a void function. The column of test data is shown below.
103.816
43.984
2214.5
321.5
615.8
8.186
37.6
The for loop in the function Read_File reads back the data from the file perfectly indicating that the function works fine and properly reads in the data. However, when I try to display the same data using the same for loop in the main program i get an EXEC_BAD_ACCESS fault. To be clear the program compiles fine, but it is not passing the data to the main program, which indicates a pointer problem. Where am I going wrong with this? Any help would be greatly appreciated.
#include <vector>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <iterator>
class Read_Columnar_File {
public:
void Read_File(const std::string& file_name,std::vector<float>& Column1);
};
#include <iostream>
int main(int argc, const char * argv[]) {
int i;
std::vector<float> Column2;
Read_Columnar_File File1;
char str[20];
std::strcpy(str,"Test.txt");
File1.Read_File(str,Column2);
std::cout << std::endl;
for(i = 0; i < 7; i++) std::cout << Column2[i];
}
void Read_Columnar_File::Read_File(const std::string& file_name,std::vector<float>& Column1)
{
int i;
std::ifstream inp(file_name,std::ios::in | std::ios::binary);
if(inp.is_open()) {
std::istream_iterator<float> start((inp)), end;
std::vector<float> Column1(start,end);
for(i=0; i < 7; i++) std::cout << Column1[i] << std::endl;
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}
you are declaring a local variable std::vector<float> Column1(start,end); inside the function
the local variable is being assigned the values, so actual vector is not updated.
This will fix the issue. column1 is locally declared.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
class Read_Columnar_File {
public:
void Read_File(const std::string& file_name, std::vector<float>& Column1);
};
int main(int argc, const char * argv[]) {
int i;
std::vector<float> Column2;
Read_Columnar_File File1;
char str[20];
std::strcpy(str, "Test.txt");
File1.Read_File(str, Column2);
std::cout << std::endl;
for (i = 0; i < 7; i++) std::cout << Column2[i];
}
void Read_Columnar_File::Read_File(const std::string& file_name, std::vector<float>& Column1)
{
int i;
std::ifstream inp(file_name, std::ios::in | std::ios::binary);
if (inp.is_open()) {
std::istream_iterator<float> start((inp)), end;
Column1.assign(start, end);
for (i = 0; i < 7; i++) std::cout << Column1[i] << std::endl;
}
else std::cout << "Cannot Open " << file_name << std::endl;
inp.close();
}

Storing data from an unknown number of files

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;
}