I wrote a program that takes a file and reads it into a stringstream field in a class, and now I'm trying to interact with it. The problem is that when reading sequentially from several methods, one of the methods gives an error, or simply does not work. I guess the problem is how I read the file, how should I improve it?
There is my class:
class MatReader
{
protected:
...
stringstream text;
...
string PhysicsMaterial;
string Diffuse;
string NMap;
string Specular;
public:
/// <summary>
/// Read all lines in .mat document by string
/// </summary>
void ReadAllLines(string file_path);
/// <summary>
/// Getting PhysicsMaterial property
/// </summary>
string getPhysMaterial();
/// <summary>
/// Getting diffuse file path
/// </summary>
string getDiffuseLocation();
};
And there is my implementation file:
#include "MaterialHandler.h"
void MatReader::ReadAllLines(string mat_file)
{
ifstream infile(mat_file);
string str;
if (infile.is_open())
{
ofile = true;
while (!infile.eof())
{
getline(infile, str);
text << str+"\n";
}
}
else
throw exception("[ERROR] file does not exist or corrupted");
}
string MatReader::getPhysMaterial()
{
string line;
vector<string> seglist;
try
{
if (ofile == false)
throw exception("file not open");
while (getline(text, line, '"'))
{
if (!line.find("/>"))
break;
seglist.push_back(line);
}
for (uint16_t i{}; i < seglist.size(); i++)
{
if (seglist[i-1] == " PhysicsMaterial=")
{
PhysicsMaterial = seglist[i];
return seglist[i];
}
}
line.clear();
seglist.clear();
}
catch (const std::exception& ex)
{
cout << "[ERROR]: " << ex.what() << endl;
return "[ERROR]";
}
}
string MatReader::getDiffuseLocation()
{
string line;
vector<string> seglist;
try
{
if (ofile == false)
throw exception("file not open");
while (getline(text, line, '"'))
{
seglist.push_back(line);
}
for (uint16_t i{}; i < seglist.size(); i++)
{
if (seglist[i - 1] == " File=")
{
PhysicsMaterial = seglist[i];
return seglist[i];
}
}
}
catch (const std::exception& ex)
{
cout << "[ERROR]: " << ex.what() << endl;
return "[ERROR]";
}
}
The methods "getPhysMaterial()" and "getDiffuseLocation()" works separately without any problems, but if they are executed sequentially, they give an error or are not executed at all.
Thank you.
So first you need to correct the issue with your out-of-range array access.
The next issue you have is you need to reset the position of the stream in order to re-read it from the beginning.
Here is an example of how you can do that.
std::stringstream ss{ };
ss << "This is line one\n"
<< "This is line two\n"
<< "This is line three\n"
<< "This is line four\n";
// Points to the start of the stringstream.
// You can store this as a member of your class.
const std::stringstream::pos_type start{ ss.tellg( ) };
std::string line{ };
while ( std::getline( ss, line ) )
std::cout << line << '\n';
// Reset to the start of the stringstream.
ss.clear( );
ss.seekg( start );
while ( std::getline( ss, line ) )
std::cout << line << '\n';
Another issue I noticed is that you're checking for eof in the loop condition. Don't do that.. Why is iostream::eof inside a loop condition (i.e. while (!stream.eof())) considered wrong?
Do something like this instead:
std::stringstream ss{ };
if ( std::ifstream file{ "/Path/To/MyFile.txt" } )
{
std::string input{ };
while ( std::getline( file, input ) )
ss << input << '\n';
}
else
{
std::cerr << "Failed to open file\n";
return 1;
}
std::string line{ };
while ( std::getline( ss, line ) )
std::cout << line << '\n';
Related
I know that there's many questions here about reading lines from a text file and storing it in a c++ struct. However, those questions all contain text files that have precisely the information they needed, such as a text file with :
Tom 123
Jim 234
However, what if my text file had something like
// The range of 'horizontal' indices, inclusive
// E.g. if the range is 0-4, then the indices are 0, 1, 2, 3, 4
GridX_IdxRange=0-8
// The range of 'vertical' indices, inclusive
// E.g. if the range is 0-3, then the indices are 0, 1, 2, 3
GridY_IdxRange=0-9
How would I be able to get just the numbers 0-8 and 0-9 besides the equal sign in gridX and gridY to be stored in my struct?
What I tried:
struct config {
int gridRange[2];
int gridX;
int gridY;
int cityId;
int cloudCover;
int pressure;
std::string cityName;
};
config openFile(std::string filename, config &con) {
std::fstream inputFile(filename.c_str(), std::fstream::in);
if (inputFile.is_open()) {
std::string line;
for (int i = 0; i < 2; i++) {
while (std::getline(inputFile, line)) {
inputFile >> con.gridRange[i];
std::cout << con.gridRange[i]; //to show what is stored in the array
}
}
//std::cout << range << std::endl;
std::cout << std::endl;
return con;
}
else {
std::cout << "Unable to open file" << std::endl;
}
}
Ignore the other variables in the struct as I still do not need them yet. The output I got was
// The range of 'horizontal' indices, inclusive
0
I managed to solve this by adding in if (line[0] != '/' && !line.empty()) { inside the while loop and creating a new string variable called lineArray[5]. The function now looks like this
config openFile(std::string filename, config &con) {
std::fstream inputFile(filename.c_str(), std::fstream::in);
if (inputFile.is_open()) {
std::string line;
std::string lineArray[5];
int count = 0;
while (std::getline(inputFile, line)) {
if (line[0] != '/' && !line.empty()) {
lineArray[count] = line;
count++;
}
}
std::cout << "Printing out " << lineArray[0] << std::endl;
std::cout << std::endl;
return con;
}
else {
std::cout << "Unable to open file" << std::endl;
}
}
And the output I'll get is Printing out GridX_IdxRange=0-8.
I'm learnig C++. Here is my problem: I'm trying to read data from a text file and save it to a map<string, struct> and then have it print out all the keys from the map preferably in alphabetical order. The data has 2 strigns and a float. I can't get this to print even after having tried many different solutions.
Heres what I've got so far:
Here is my struct:
struct category
{
std::string tram_stop;
float dist;
};
using Tram = std::map<std::string, std::vector<category>>;
Here is where I try to save the data to the map.
void store(Tram& tram, std::vector<std::string>& tram_data)
{
if (tram.find (tram_data.at (0)) == tram.end ())
{
tram[tram_data.at (0)] = {};
}
else
{
tram.at (tram_data.at (0)).push_back (category {tram_data.at (1), std::stof(tram_data.at(2))});
}
}
And here is main().
int main()
{
Tram tram;
print_rasse();
// Ask input filename.
std::string filename;
std::cout << "Give a name for input file: ";
std::cin >> filename;
// Read input file.
std::ifstream file_in;
file_in.open (filename);
if (!file_in.is_open ())
{
std::cout << INVALID_FILE << std::endl;
return EXIT_FAILURE;
}
std::vector<std::string> tram_data;
if (file_in.is_open())
{
std::string line;
while( std::getline(file_in,line) )
{
std::stringstream ss(line);
std::string tram_line, tram_stop, distance;
std::getline(ss,tram_line,';'); //std::cout<< ""<<tram_line <<" ";
std::getline(ss,tram_stop,';'); //std::cout<<" "<<tram_stop<<" ";
std::getline(ss,distance); //std::cout<<" "<<distance<< " ";
if (tram_line != "" && tram_stop != "")
{
tram_data.push_back (tram_line);
tram_data.push_back (tram_stop);
tram_data.push_back (distance);
//std::cout << tram_line << " " << distance << std::endl;
}
else
{
std::cout << INVALID_FORMAT << std::endl;
return EXIT_FAILURE;
}
}
file_in.close ();
store(tram, tram_data);
}
This is the part I think doesn't work. Tried different iterators too.
if (upper_com == "LINES")
{
std::cout << "All tramlines in alphabetical order:" << std::endl;
for (auto& item : tram)
{
std::cout << item.first << std::endl;
}
}
Your implementation of store will create a vector for the first item added for a particular tram_data[0] value, but will not add anything to the vector. This results in losing that first item, and can result in no output because of the empty vectors.
That function can be simplified:
void store(Tram& tram, std::vector<std::string>& tram_data)
{
if (tram_data.size() < 3) throw std::out_of_range();
tram[tram_data[0]].emplace_back(tram_data[1], std::stof(tram_data[2]));
}
You don't need to use at with tram because you want to create the entry if it doesn't exist. at with tram_data will result in an exception being thrown if there are fewer than three elements in tram_data, so that check has been moved outside all the accesses to the vector.
I have an error message for the string class. Based on examples I have found through trying to solve this, I believe I am using the class correctly.
Below is the code :
int main()
{
string allData, gridNum;
ifstream gridData;
gridData.open ("/Users/Neo/Documents/UNi/Year_3/Grid Data Analysis Program/gridData.txt");
if (gridData.is_open())
{
while ( getline (gridData, allData) )
{
size_t gridNum = allData.find("Grid Receiver 34");
string receiverX = allData.substr (gridNum, 40);
cout << receiverX << endl;
}
gridData.close();
}
else cout << "Unable to open file..." << endl;
return 0;
}
error in the console...
libc++abi.dylib: terminating with uncaught exception of type std::out_of_range: basic_string
(lldb)
I am trying to read from a text file into a string variable. I only want to read in 40 characters after the words "Grid receiver 34", then print the contents of the new string.
while ( getline (gridData, allData) )
{
size_t gridNum = allData.find("Grid Receiver 34");
string receiverX = allData.substr (gridNum, 40);
cout << receiverX << endl;
}
Here you read the file line by line, searching for "Grid Receiver 34", however, if that string isn't found then std::string::find will return std::string::npos. Using that as argument for substr gets you in trouble. You should check if it's found before using it:
while ( getline (gridData, allData) )
{
size_t gridNum = allData.find("Grid Receiver 34");
if(gridNum != std::string::npos)
{
string receiverX = allData.substr (gridNum, 40);
cout << receiverX << endl;
}
}
Also, stop using using namespace std;.
You're probably getting an exception on the lines where the search string is not found.
You want to only try to extract the substring on lines where the string is found.
Modify your code as follows:
int main()
{
string allData, gridNum;
ifstream gridData;
gridData.open ("/Users/Neo/Documents/UNi/Year_3/Grid Data Analysis Program/gridData.txt");
if (gridData.is_open())
{
while ( getline (gridData, allData) )
{
size_t gridNum = allData.find("Grid Receiver 34");
if (gridNum != std::string::npos) // add this condition :-)
{
string receiverX = allData.substr (gridNum, 40);
cout << receiverX << endl;
}
}
gridData.close();
}
else cout << "Unable to open file..." << endl;
return 0;
}
here is my problem. i have some two dimensional data with changing dimensionality, that i want to read into an 2d-array of doubles. Furthermore, there are at some points not number in the file but "NaN"s, that i want to be replaced by a zero. I made my code working so far, but i only managed to read integers. Maybe you could help me out to read it as doubles?
Here is what i got so far:
void READER(char filepath [], int target [129][128])
{
//---------------------------- header double & int
int rowA = 0;
int colA = 0;
std::string line;
std::string x;
std::cout << "reading file: " << filepath << "\n";
std::cout << std::endl;
std::ifstream fileIN;
fileIN.open(filepath);
if (!fileIN.good())
std::cerr << "READING ERROR IN FILE: " << filepath << std::endl;
while (fileIN.good())
{
while (getline(fileIN, line))
{
std::istringstream streamA(line);
colA = 0;
while (streamA >> x)
{
boost::algorithm::replace_all(x, "NaN", "0");
boost::algorithm::replace_all(x, ",", ""); //. rein
// std::cout << string_to_int(x) << std::endl;
target [rowA][colA] = string_to_int(x);
colA++;
}
rowA++;
if(rowA%5 ==0)
{
std::cout << "*";
}
}
}
std::cout << " done." <<std::endl;
}
this writes the files into 'target'. The function string to int looks the following:
int string_to_int (const std::string& s)
{
std::istringstream i(s);
int x;
if(!(i >> x))
return 0;
return x;
}
here you find some example data:
"exactly, thats what i thought about doing with the line boost::algorithm::replace_all(x, ",", ""); by replacing , by ."
Use following function to convert to any type, say double :-
template <typename T>
T StringToNumber ( const std::string &Text )
{
std::istringstream ss(Text);
T result;
return ss >> result ? result : 0;
}
Call using :
boost::algorithm::replace_all(x, ",", "."); // Change , to .
std::cout << StringToNumber<double>(x) << std::endl;
Or
you can simply use boost::lexical_cast
std::cout<<boost::lexical_cast<double>( x )<<std::endl;
Make sure you have a double 2D array
I want to have the option to print out the last 10 lines of a textfile . with this program I've been able to read the whole textfile, but I can't figure out how to manipulate the array in which the textfile is saved, any help?
// Textfile output
#include<fstream>
#include<iostream>
#include<iomanip>
using namespace std;
int main() {
int i=1;
char zeile[250], file[50];
cout << "filename:" << flush;
cin.get(file,50); ///// (1)
ifstream eingabe(datei , ios::in); /////(2)
if (eingabe.good() ) { /////(3)
eingabe.seekg(0L,ios::end); ////(4)
cout << "file:"<< file << "\t"
<< eingabe.tellg() << " Bytes" ////(5)
<< endl;
for (int j=0; j<80;j++)
cout << "_";
cout << endl;
eingabe.seekg(0L, ios::beg); ////(6)
while (!eingabe.eof() ){ ///(7)
eingabe.getline(zeile,250); ///(8)
cout << setw(2) << i++
<< ":" << zeile << endl;
}
}
else
cout <<"dateifehler oder Datei nicht gefunden!"
<< endl;
return 0;
}
Try this:
#include <list>
#include <string>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iterator>
// A class that knows how to read a line using operator >>
struct Line
{
std::string theLine;
operator std::string const& () const { return theLine; }
friend std::istream& operator>>(std::istream& stream, Line& l)
{
return std::getline(stream, l.theLine);
}
};
// A circular buffer that only saves the last n lines.
class Buffer
{
public:
Buffer(size_t lc)
: lineCount(lc)
{}
void push_back(std::string const& line)
{
buffer.insert(buffer.end(),line);
if (buffer.size() > lineCount)
{
buffer.erase(buffer.begin());
}
}
typedef std::list<std::string> Cont;
typedef Cont::const_iterator const_iterator;
typedef Cont::const_reference const_reference;
const_iterator begin() const { return buffer.begin(); }
const_iterator end() const { return buffer.end();}
private:
size_t lineCount;
std::list<std::string> buffer;
};
// Main
int main()
{
std::ifstream file("Plop");
Buffer buffer(10);
// Copy the file into the special buffer.
std::copy(std::istream_iterator<Line>(file), std::istream_iterator<Line>(),
std::back_inserter(buffer));
// Copy the buffer (which only has the last 10 lines)
// to std::cout
std::copy(buffer.begin(), buffer.end(),
std::ostream_iterator<std::string>(std::cout, "\n"));
}
Basically, you are not saving the file contents to any array. The following sample will give you a head start:
#include <iostream>
#include <vector>
#include <string>
int main ( int, char ** )
{
// Ask user for path to file.
std::string path;
std::cout << "filename:";
std::getline(std::cin, path);
// Open selected file.
std::ifstream file(path.c_str());
if ( !file.is_open() )
{
std::cerr << "Failed to open '" << path << "'." << std::endl;
return EXIT_FAILURE;
}
// Read lines (note: stores all of it in memory, might not be your best option).
std::vector<std::string> lines;
for ( std::string line; std::getline(file,line); )
{
lines.push_back(line);
}
// Print out (up to) last ten lines.
for ( std::size_t i = std::min(lines.size(), std::size_t(10)); i < lines.size(); ++i )
{
std::cout << lines[i] << std::endl;
}
}
It would probably be wiser to avoid storing the whole file into memory, so you could re-write the last 2 segments this way:
// Read up to 10 lines, accumulating.
std::deque<std::string> lines;
for ( std::string line; lines.size() < 0 && getline(file,line); )
{
lines.push_back(line);
}
// Read the rest of the file, adding one, dumping one.
for ( std::string line; getline(file,line); )
{
lines.pop_front();
lines.push_back(line);
}
// Print out whatever is left (up to 10 lines).
for ( std::size_t i = 0; i < lines.size(); ++i )
{
std::cout << lines[i] << std::endl;
}
The eof() function does not do what you and it seems a million other C++ newbies think it does. It does NOT predict if the next read will work. In C++ as in any other language, you must check the status of each read operation, not the state of the input stream before the read. so the canonical C++ read line loop is:
while ( eingabe.getline(zeile,250) ) {
// do something with zeile
}
Also, you should be reading into a std::string, and get rid of that 250 value.
Do a circular buffer with 10 slots and while reading the file lines, putting them into this buffer. When you finish thr file, do a position++ to go to the first element and print them all.
Pay attention for null values if the file has less than 10 lines.
Have an array of strings with size 10.
Read the first line and store into the array
Continue reading till the array is full
Once the array is full delete the first entry so that you can enter new line
Repeate step 3 and 4 till the file is finished reading.
I investigate proposed approaches here and describe all in my blog post. There is a better solution but you have to jump to the end and persist all needed lines:
std::ifstream hndl(filename, std::ios::in | std::ios::ate);
// and use handler in function which iterate backward
void print_last_lines_using_circular_buffer(std::ifstream& stream, int lines)
{
circular_buffer<std::string> buffer(lines);
std::copy(std::istream_iterator<line>(stream),
std::istream_iterator<line>(),
std::back_inserter(buffer));
std::copy(buffer.begin(), buffer.end(),
std::ostream_iterator<std::string>(std::cout));
}