The content of my test csv file looks as follows:
*test.csv*
name;age;weight;height;test
Bla;32;1.2;4.3;True
Foo;43;2.2;5.3;False
Bar;None;3.8;2.4;True
Ufo;32;1.5;5.4;True
I load the test.csv file with the following C++ program that prints the file's content on the screen:
#include <iostream>
#include <vector>
#include <string>
#include <iomanip>
#include <fstream>
#include <sstream>
void readCSV(std::vector<std::vector<std::string> > &data, std::string filename);
void printCSV(const std::vector<std::vector<std::string>> &data);
int main(int argc, char** argv) {
std::string file_path = "./test.csv";
std::vector<std::vector<std::string> > data;
readCSV(data, file_path);
printCSV(data);
return 0;
}
void readCSV(std::vector<std::vector<std::string> > &data, std::string filename) {
char delimiter = ';';
std::string line;
std::string item;
std::ifstream file(filename);
while (std::getline(file, line)) {
std::vector<std::string> row;
std::stringstream string_stream(line);
while (std::getline(string_stream, item, delimiter)) {
row.push_back(item);
}
data.push_back(row);
}
file.close();
}
void printCSV(const std::vector<std::vector<std::string> > &data) {
for (std::vector<std::string> row: data) {
for (std::string item: row) {
std::cout << item << ' ';
}
std::cout << std::endl;
}
}
I have the following questions:
How can I load only rows where, for example, age == 32?
How can I load, for example, only the name, weight columns?
How can I exclude rows that contain None?
How can I skip the first row of the document?
Does it make more sense to extract the desired information after I loaded the entire csv file (if memory is not a problem)? If possible, I want to use only the STL.
Any assistance you can provide would be greatly appreciated
you can try some csv libraries, but if you want to do this with custom code then
Inside printCSV you ask the cin to enter column names
Maintain it in a variable
In this code for (std::vector<std::string> row: data)
Check the item again each of those input when first the loop runs
then inside the second loop keep an index, accordingly you skip the column number
Example code to print only two columns
void printCSV(const std::vector<std::vector<std::string> > &data) {
int col = 0;
std::vector<std::string> column_filter;
std::vector<int> column_index;
column_filter.push_back("name");
column_filter.push_back("weight");
int row1 =0;
for (std::vector<std::string> row: data) {
col = 0;
if(row1==0) {
int col1 = 0;
for (std::string item: row) {
for (std::string colname: column_filter){
if(item.compare(colname)==0) {
column_index.push_back(col1);
}
}
col1++;
}
}
for (std::string item: row) {
int found =0;
for (int index: column_index) {
if(index==col) found = 1;
}
if(found==1)
std::cout << item << ' ';
col++;
}
std::cout << std::endl;
row1++;
}
}
Output
name weight
Bla 1.2
Foo 2.2
Bar 3.8
Ufo 1.5
Before you close. Here all answers in one file. But I will then explain in your single questions then later.
#include <iostream>
#include <vector>
#include <string>
#include <iomanip>
#include <fstream>
#include <regex>
#include <algorithm>
#include <iterator>
// Save typing Work
using Rec = std::vector<std::string>;
std::regex delimiter{ ";" };
// Proxy class for easier input and output
struct Record {
// Our data for one record
Rec data{};
// Overwrite extractor operator
friend std::istream& operator >> (std::istream& is, Record& r) {
// Read on complete line from the input stream, and check, if the read was successfull
if (std::string line{}; std::getline(is, line)) {
// If there is something in our data vector already, delete it
r.data.clear();
// Now, in one statement, split the string into tokens and copy the result into our data vector
std::copy(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {}, std::back_inserter(r.data));
}
return is;
}
// Overwrite inserter for easier output
friend std::ostream& operator << (std::ostream& os, const Record& r) {
std::copy(r.data.begin(), r.data.end(), std::ostream_iterator<std::string>(os,"\t"));
return os;
}
};
// Proxy for the complete CSV file
struct Roster {
// The header
Rec header{};
// All records of the CSV file
std::vector<Record> records{};
friend std::istream& operator >> (std::istream& is, Roster& r) {
// Read on complete line from the input stream, and check, if the read was successfull
if (std::string line{}; std::getline(is, line)) {
// So, we just have read the header
// Now, in one statement, split the string into tokens and copy the result into the header
r.header.clear();
std::copy(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {}, std::back_inserter(r.header));
// Now, in one statement, read all lines, split the string into tokens and copy the result into our record vector
r.records.clear();
std::copy(std::istream_iterator<Record>(is), {}, std::back_inserter(r.records));
}
return is;
}
// Overwrite inserter for easier output
friend std::ostream& operator << (std::ostream& os, const Roster& r) {
std::copy(r.records.begin(), r.records.end(), std::ostream_iterator<Record>(os, "\n"));
return os;
}
};
int main() {
// Open CSV file and check, if it could be opened
if (std::ifstream csvFileStream("r:\\test.csv"); csvFileStream) {
Roster roster{};
// Read the complete CSV file
csvFileStream >> roster;
// Show all read data on std::cout
std::cout << roster;
// All records with age ==32
std::cout << "\n\nAge 32\n";
std::vector<Record> age32{};
std::copy_if(roster.records.begin(), roster.records.end(), std::back_inserter(age32), [](const Record& r) { return r.data[1] == "32"; });
for (const Record& r : age32) std::cout << r << "\n";
// Or
std::cout << "\n\nAge 32 Option 2\n";
csvFileStream.clear(); csvFileStream.seekg(std::ios::beg); age32.clear();
std::copy_if(std::istream_iterator<Record>(csvFileStream), {}, std::back_inserter(age32), [](const Record& r) { return r.data[1] == "32"; });
for (const Record& r : age32) std::cout << r << "\n";
// Get Name and weight columns
std::cout << "\n\nweight and columns\n";
std::vector<std::vector<std::string>> nameAndWeight{};
std::transform(roster.records.begin(), roster.records.end(), std::back_inserter(nameAndWeight),
[](const Record& r) { std::vector<std::string>rec{ r.data[0], r.data[2] } ; return rec; });
for (const std::vector<std::string>& r : nameAndWeight) std::cout << r[0] << "\t" << r[1] << "\n";
// Everything but none
std::cout << "\n\nEverything but none\n";
std::vector<Record> notNone{};
std::copy_if(roster.records.begin(), roster.records.end(), std::back_inserter(notNone), [](const Record& r) { return r.data[1] != "None"; });
for (const Record& r : notNone) std::cout << r << "\n";
}
else {
std::cerr << "\n*** Error: Could not open source file\n";
}
return 0;
}
Related
I have a txt file which contains two txt file references ei. main.txt contains eg1.txt and eg2.txt and i have to access the content in them and find the occurences of every word and return a string with the word and the documents it was preasent in(0 being eg1.txt and 1 being eg2.txt). My program compiles but I can't get past the first word I encounter. It gives the right result (word: 0 1) since the word is preasent in both the files and in the first position but it doesn't return the other words. Could someone please help me find the error? Thank you
string func(string filename) {
map<string, set<int> > invInd;
string line, word;
int fileNum = 0;
ifstream list (filename, ifstream::in);
while (!list.eof()) {
string fileName;
getline(list, fileName);
ifstream input_file(fileName, ifstream::in); //function to iterate through file
if (input_file.is_open()) {
while (getline(input_file, line)) {
stringstream ss(line);
while (ss >> word) {
if (invInd.find(word) != invInd.end()) {
set<int>&s_ref = invInd[word];
s_ref.insert(fileNum);
}
else {
set<int> s;
s.insert(fileNum);
invInd.insert(make_pair<string, set<int> >(string(word) , s));
}
}
}
input_file.close();
}
fileNum++;
}
Basically your function works. It is a little bit complicated, but i works.
After removing some syntax errors, the main problem is, that you do return nothing from you function. There is also no output statement.
Let me show you you the corrected function which shows some output.
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <set>
#include <sstream>
#include <utility>
using namespace std;
void func(string filename) {
map<string, set<int> > invInd;
string line, word;
int fileNum = 0;
ifstream list(filename, ifstream::in);
while (!list.eof()) {
string fileName;
getline(list, fileName);
ifstream input_file(fileName, ifstream::in); //function to iterate through file
if (input_file.is_open()) {
while (getline(input_file, line)) {
stringstream ss(line);
while (ss >> word) {
if (invInd.find(word) != invInd.end()) {
set<int>& s_ref = invInd[word];
s_ref.insert(fileNum);
}
else {
set<int> s;
s.insert(fileNum);
invInd.insert(make_pair(string(word), s));
}
}
}
input_file.close();
}
fileNum++;
}
// Show the output
for (const auto& [word, fileNumbers] : invInd) {
std::cout << word << " : ";
for (const int fileNumber : fileNumbers) std::cout << fileNumber << ' ';
std::cout << '\n';
}
return;
}
int main() {
func("files.txt");
}
This works, I tested it. But maybe you want to return the findings to your main function. Then you should write:
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <set>
#include <sstream>
#include <utility>
using namespace std;
map<string, set<int> > func(string filename) {
map<string, set<int> > invInd;
string line, word;
int fileNum = 0;
ifstream list(filename, ifstream::in);
while (!list.eof()) {
string fileName;
getline(list, fileName);
ifstream input_file(fileName, ifstream::in); //function to iterate through file
if (input_file.is_open()) {
while (getline(input_file, line)) {
stringstream ss(line);
while (ss >> word) {
if (invInd.find(word) != invInd.end()) {
set<int>& s_ref = invInd[word];
s_ref.insert(fileNum);
}
else {
set<int> s;
s.insert(fileNum);
invInd.insert(make_pair(string(word), s));
}
}
}
input_file.close();
}
fileNum++;
}
return invInd;
}
int main() {
map<string, set<int>> data = func("files.txt");
// Show the output
for (const auto& [word, fileNumbers] : data) {
std::cout << word << " : ";
for (const int fileNumber : fileNumbers) std::cout << fileNumber << ' ';
std::cout << '\n';
}
}
Please enable C++17 in your compiler.
And please see below a brushed up solution. A little bit cleaner and compacter, with comments and better variable names.
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <set>
#include <sstream>
#include <utility>
using WordFileIndicator = std::map<std::string, std::set<int>>;
WordFileIndicator getWordsWithFiles(const std::string& fileNameForFileLists) {
// Here will stor the resulting output
WordFileIndicator wordFileIndicator{};
// Open the file and check, if it could be opened
if (std::ifstream istreamForFileList{ fileNameForFileLists }; istreamForFileList) {
// File number Reference
int fileNumber{};
// Read all filenames from the list of filenames
for (std::string fileName{}; std::getline(istreamForFileList, fileName) and not fileName.empty();) {
// Open the files to read their content. Check, if the file could be opened
if (std::ifstream ifs{ fileName }; ifs) {
// Add word and associated file number to set
for (std::string word{}; ifs >> word; )
wordFileIndicator[word].insert(fileNumber);
}
else std::cerr << "\n*** Error: Could not open '" << fileName << "'\n\n";
// Continue with next file
++fileNumber;
}
}
else std::cerr << "\n*** Error: Could not open '" << fileNameForFileLists << "'\n\n";
return wordFileIndicator;
}
// Some test code
int main() {
// Get result. All words and in which file they exists
WordFileIndicator data = getWordsWithFiles("files.txt");
// Show the output
for (const auto& [word, fileNumbers] : data) {
std::cout << word << " : ";
for (const int fileNumber : fileNumbers) std::cout << fileNumber << ' ';
std::cout << '\n';
}
}
There would be a much faster solution by using std::unordered_map and std::unordered_set
Please make sure your code is composed from many small functions. This improves readability, it easier to reason what code does, in such form parts of code can be reused in alternative context.
Here is demo how it can looks like and why it is better to have small functions:
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <unordered_map>
#include <vector>
struct FileData
{
std::filesystem::path path;
int index;
};
bool operator==(const FileData& a, const FileData& b)
{
return a.index == b.index && a.path == b.path;
}
bool operator!=(const FileData& a, const FileData& b)
{
return !(a == b);
}
using WordLocations = std::unordered_map<std::string, std::vector<FileData>>;
template<typename T>
void mergeWordsFrom(WordLocations& loc, const FileData& fileData, T b, T e)
{
for (; b != e; ++b)
{
auto& v = loc[*b];
if (v.empty() || v.back() != fileData)
v.push_back(fileData);
}
}
void mergeWordsFrom(WordLocations& loc, const FileData& fileData, std::istream& in)
{
return mergeWordsFrom(loc, fileData, std::istream_iterator<std::string>{in}, {});
}
void mergeWordsFrom(WordLocations& loc, const FileData& fileData)
{
std::ifstream f{fileData.path};
return mergeWordsFrom(loc, fileData, f);
}
template<typename T>
WordLocations wordLocationsFromFileList(T b, T e)
{
WordLocations loc;
FileData fileData{{}, 0};
for (; b != e; ++b)
{
++fileData.index;
fileData.path = *b;
mergeWordsFrom(loc, fileData);
}
return loc;
}
WordLocations wordLocationsFromFileList(std::istream& in)
{
return wordLocationsFromFileList(std::istream_iterator<std::filesystem::path>{in}, {});
}
WordLocations wordLocationsFromFileList(const std::filesystem::path& p)
{
std::ifstream f{p};
f.exceptions(std::ifstream::badbit);
return wordLocationsFromFileList(f);
}
void printLocations(std::ostream& out, const WordLocations& locations)
{
for (auto& [word, filesData] : locations)
{
out << std::setw(10) << word << ": ";
for (auto& file : filesData)
{
out << std::setw(3) << file.index << ':' << file.path << ", ";
}
out << '\n';
}
}
int main()
{
auto locations = wordLocationsFromFileList("files.txt");
printLocations(std::cout, locations);
}
https://wandbox.org/permlink/nBbqYV986EsqvN3t
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 have next c++ class called "Contact":
class Contact
{
private:
std::string contactName;
double subscriptionPrice;
int minutesIncluded;
public:
Contact(const std::string &contactName, double subscriptionPrice,
int minutesIncluded) : contactName(contactName), subscriptionPrice(subscriptionPrice), minutesIncluded(minutesIncluded)) {}
Contact() {
}
...gettetrs and setters
}
I have text file with one or more contacts in format:
asd,1.00000,1
In main method I have method that add properly vector of contacts in this text file. Problem is when I try to read from it. My target is to convert text file into vector of contacts. Method I use is next:
void phonebook_load(vector<Contact> &contacts)
{
string line;
ifstream phonebook_file;
vector<std::string> lines;
phonebook_file.open(phonebook_filename);
if(!phonebook_file.is_open())
cout << "Phonebook file could not be openned !!!" << endl;
else
{
while (phonebook_file.good())
{
for (string line; getline(phonebook_file, line, ','); )
lines.push_back(line);
}
phonebook_file.close();
}
}
I have two options:
Read line by line (which I cannot split by ",")
Split by "," which print every property of contact on new line, and I don't see how tho handle it from there.
What should I change in my method in order to read file line by line and properly convert it to vector<Contact>
Provide stream extraction and stream insertion operators for your type:
#include <string>
#include <vector>
#include <iterator>
#include <fstream>
#include <iostream>
class Contact
{
private:
std::string contactName;
double subscriptionPrice;
int minutesIncluded;
public:
Contact() {}
Contact(const std::string &contactName, double subscriptionPrice, int minutesIncluded)
: contactName { contactName },
subscriptionPrice { subscriptionPrice },
minutesIncluded { minutesIncluded }
{}
// declare the stream extraction and stream insertion operators as firends
// of your class to give them direct access to members without the need for
// getter and setter functions.
friend std::istream& operator>>(std::istream &is, Contact &contact);
friend std::ostream& operator<<(std::ostream &os, Contact const &contact);
};
std::istream& operator>>(std::istream &is, Contact &contact)
{
std::string contact_name;
if (!std::getline(is, contact_name, ',')) // use getline with a delimiter
return is; // to allow whitespace in names
// which >> doesn't
char seperator;
double subscription_price;
int minutes_included;
if (!(is >> subscription_price >> seperator >> minutes_included) || seperator != ',') {
is.setstate(std::ios::failbit);
return is;
}
contact = Contact{ contact_name, subscription_price, minutes_included };
return is;
}
std::ostream& operator<<(std::ostream &os, Contact const &contact)
{
os << contact.contactName << ", " << std::fixed << contact.subscriptionPrice
<< ", " << contact.minutesIncluded;
return os;
}
int main()
{
std::ifstream is{ "test.txt" };
std::vector<Contact> contacts{ std::istream_iterator<Contact>{ is },
std::istream_iterator<Contact>{} };
for (auto const &c : contacts)
std::cout << c << '\n';
}
I am opening a csv file which contains entries in the form "key,value".
Task is to find a particular key and modify its corresponding value with another string. (key & value : both are strings).
int main()
{
std::fstream m_file;
m_file.open("input.csv", std::ios::in | std::ios::out);
m_file << "Star,Treks\n";
m_file << "Captain,America\n";
m_file << "Black,Cats\n";
m_file << "Ninja,Fighters\n";
std::string row;
std::string key = "Black";
std::string value = "Dreamers";
while (std::getline(m_file, row)) {
std::size_t pos = row.find(',');
std::string retrieved_key = row.substr(0, pos);
std::string retrieved_value = row.substr(pos + 1);
if (retrieved_key == key) {
std::size_t currentPos = m_file.tellg();
std::size_t row_length = row.length();
m_file.seekp(currentPos - row_length);
std::string newEntry = key + "," + value + "\n";
m_file << newEntry;
return 0;
}
}
getchar();
return 0;
}
This is the code I have written. But it does not work. It modifies the contents of the file as below :
Star,Treks
Captain,America
BlBlack,Dreamers
Fighters
You cannot modify files directly using seekg() and tellg() functions unless these have a fixed size format of the parts you want to change.
The usual way to do such things is to create an intermediary temp file to write your changes, and replace the original one with it after you're done.
Recipe:
#include <cstdlib>
#include <iostream>
#include <utility>
#include <string>
#include <fstream>
#include <vector>
#include <iterator>
#include <algorithm>
class pair : public std::pair<std::string, std::string>
{
public:
using std::pair<std::string, std::string>::pair;
bool operator==(pair const & rhs) const { return first == rhs.first; }
friend std::istream& operator>>(std::istream &is, pair &p)
{
std::getline(is, p.first, ',');
std::getline(is, p.second, '\n');
return is;
}
friend std::ostream& operator<<(std::ostream &os, pair const &p)
{
os << p.first << ',' << p.second << '\n';
return os;
}
};
int main()
{
char const *filename = "input.csv";
std::fstream file{ filename, std::ios::in };
if (!file) {
std::cerr << "Failed to open file \"" << filename << "\"!\n\n";
return EXIT_FAILURE;
}
std::vector<pair> data{
std::istream_iterator<pair>{ file },
std::istream_iterator<pair>{}
};
file.close();
auto it{ std::find(data.begin(), data.end(), pair{ "Black", "" }) };
if (it == data.end()) {
std::cerr << "Key not found!\n\n";
return EXIT_FAILURE;
}
it->second = "Dreamers";
file.open(filename, std::ios::out | std::ios::trunc);
if (!file) {
std::cerr << "Failed to open file \"" << filename << "\"!\n\n";
return EXIT_FAILURE;
}
for (auto const & entry : data)
file << entry;
}
I am having trouble using push_back for vectors in c++.
My vector is named data.
In my loop I want to add 50 to data[i].getQuantity then push_back to data
These are things that I have tried.
data.push_back(data[i].getQuantity());
and
float myFloat = data[i].getQuantity() + 50;
data.push_back(data[i].getQuantity(myFloat));
data.push_back(myFloat);
The error is saying
No function to call to push_back
Here is my code:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
struct Input
{
friend std::istream& operator >>(std::istream& inp, Input& item);
friend std::ostream& operator <<(std::ostream& outp, Input const& item);
std::string group;
std::string total_pay;
float quantity;
// default constructor. sets up zero-elements
Input() : group(), total_pay(), quantity()
{
}
Input(std::string groupIn, std::string total_payIn, float quantityIn) :
group(std::move(groupIn)),
total_pay(total_payIn),
quantity(quantityIn)
{
}
// Accessors
std::string const& getGroup() const { return group; }
std::string getTotalPay() const { return total_pay; }
float getQuantity() const { return quantity; }
};
// global free function for extracting an Input item from an input stream
std::istream& operator >>(std::istream& inp, Input& item)
{
return (inp >> item.group >> item.total_pay >> item.quantity);
}
// global operator for inserting to a stream
std::ostream& operator <<(std::ostream& outp, Input const& item)
{
outp
<< item.getGroup() << ", "
<< item.getTotalPay() << ", "
<< item.getQuantity();
return outp;
}
struct ctype : std::ctype<char>
{
static mask* make_table()
{
static std::vector<mask> table(classic_table(),
classic_table() + table_size);
table[','] |= space;
return &table[0];
}
ctype() : std::ctype<char>(make_table()) { }
};
int main() {
std::fstream infile("employee.dat");
std::vector<Input> data;
std::string line;
try {
while (std::getline(infile, line))
{
std::istringstream iss(line);
Input inp;
iss.imbue(std::locale(iss.getloc(), new ctype));
while (iss >> inp) // calls our extraction operator >>
data.push_back(inp);
if (iss.fail() && !iss.eof())
std::cerr << "Invalid input line: " << line << '\n';
}
// dump all of them to stdout. calls our insertion operator <<
std::copy(data.begin(), data.end(),
std::ostream_iterator<Input>(std::cout,"\n"));
std::ofstream outp("output.dat");
for(int i = 0; i < data[i].getQuantity(); i++)
{
float myFloat = data[i].getQuantity() + 50;
data.push_back(myFloat);
outp << data[i].getGroup() << ',' << data[i].getTotalPay() << ',' << data[i].getQuantity() + 50 << '\n';
}
} catch (std::exception& e) {
std::cout << "There was an error: " << '\n';
return 1;
}
return 0;
}
Your vector is of type std::vector<Input>. That means you can only put objects of type Input into it. You can't push_back a float into such a vector.
If your intention is to create a new Input object and push that back into your vector, you could do something like
data.push_back(Input(data[i].getGroup(), data[i].getTotalPay(), data[i].getQuantity() + 50))
On the other hand, if you are simply trying to modify an element in data without adding a new element to data, you could just do
data[i].quantity += 50;
This works because you use a struct rather than a class. In a struct, variables default privacy level is public. If you wanted to use a class, or you just don't want to directly access the struct members, you would have to create a setter function for quantity.