I am working on a project and have gotten completely stuck. Basically I have to read input from a file and print to an output file. I have already done all this, but now, the variables are supposed to be arrays, and I am stuck.
Here are the instructions:
Add a new function: collectData
(a) This function should take in the plans, comics, and input file stream as parameters
(b) It should contain a loop that calls getPlan and getComics until the arrays are filled up
(c) You should use some sort of counter variable to track which array element you're on.
here is my code: (I am confused on how to fill the arrays)
void collectData(char plan[], ifstream& fin, int SIZE) {
while (!fin.eof( )) {
getPlan(plan[],fin);
for(int i=1; i<SIZE ; i++) {
plan[i] = ;
}
}
}
Typically when I'm reading and writing data from files, especially when reading them;
I like to create a Data Structure that will represent the information that I'm pulling from the file. You will have to know how the file is structured in order to read the contents from it. Typically there are 3 different ways; you can read a single line at a time, you can read line by line until all lines have been read, or you can read everything from the file all in one go. There are ways to read different amount of bytes but that's a little more complicated and beyond the scope of this design process. What I normally do is; I'll read the contents from the file and store them into either a string, or a set of strings. Then after I retrieved the information from the file; I can then close it and be done with it. Once I have that information stored; then I will parse the string data, and from there I will then populate my Data Structures on the parsed data. I like breaking things down into individual functions to separate their logic and responsibility.
Your code structure may look something like this:
struct MyDataType {
// the contents that you will store from a file.
};
// A basic method to split a string based on a single delimiter
std::vector<std::string> splitString( const std::string& s, char delimiter ) {
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream( s );
while( std::getline( tokenStream, token, delimiter ) ) {
tokens.push_back( token );
}
return tokens;
}
// Similar to above but with the ability to use a string as a delimiter as opposed to just a single char
std::vector<std::string> splitString( const std::string& strStringToSplit, const std::string& strDelimiter, const bool keepEmpty = true ) {
std::vector<std::string> tokens;
if( strDelimiter.empty() ) {
tokens.push_back( strStringToSplit );
return tokens;
}
std::string::const_iterator itSubStrStart = strStringToSplit.begin(), itSubStrEnd;
while( true ) {
itSubStrEnd = search( itSubStrStart, strStringToSplit.end(), strDelimiter.begin(), strDelimiter.end() );
std::string strTemp( itSubStrStart, itSubStrEnd );
if( keepEmpty || !strTemp.empty() ) {
tokens.push_back( strTemp );
}
if( itSubStrEnd == strStringToSplit.end() ) {
break;
}
itSubStrStart = itSubStrEnd + strDelimiter.size();
}
return tokens;
}
// This function will open a file, read a single line
// closes the file handle and returns that line as a std::string
std::string getLineFromFile( const char* filename ) {
std::ifstream file( filename );
if( !file ) {
std::stringstream stream;
stream << "failed to open file " << filename << '\n';
throw std::runtime_error( stream.str() );
}
std::string line;
std::getline( file, line );
file.close();
return line;
}
// This function will open a file and read the file line by line
// storing each line as a string and closes the file then returns
// the contents as a std::vector<std::string>
void getAllLinesFromFile( const char* filename, std::vector<std::string>& output ) {
std::ifstream file( filename );
if( !file ) {
std::stringstream stream;
stream << "failed to open file " << filename << '\n';
throw std::runtime_error( stream.str() );
}
std::string line;
while( std::getline( file, line ) ) {
if( line.size() > 0 )
output.push_back( line );
}
file.close();
}
// This function will open a file and read all of the file's contents and store it into
// a large buffer or a single string.
void getDataFromFile( const char* filename, std::string& output ) {
std::ifstream file( filename );
if( !file ) {
std::stringstream stream;
stream << "failed to open file " << filename << '\n';
throw std::runtime_error( stream.str() );
}
std::stringstream buf;
buf << file.rdbuf();
output.clear();
output.reserve( buf.str().length() );
output = buf.str();
}
// The declaration of this can vary too; depending on if you are doing a single line
// from the file, doing single line at a time for the entire file, or reading all
// of the contents from a large buffer.
void parseDataFromFile( const std::string& fileContents, std::vector<std::string>& output, std::vector<MyDataStructure>& data ) {
// This will vary on the file's data structure,
// but this is where you will call either of the `splitString` functions
// to tokenize the data.
// You will also use the `std::string's` conversion utilities such as
// std::stoi(...)... to convert to your basic types
// then you will create an instance of your data type structure
// and push that into the vector passed in.
}
Then your main would look something like this: I'll use the line by line version
int main() {
try {
std::string fileContents;
getAllinesFromFile( "test.txt", fileContents );
std::vector<std::string> tokens;
std::vector<MyDataStructure> data;
parseDataFromFile( fileContents, tokens, data );
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
This allows the code to be readable, more modular, reusable, and in some ways even generic. It also helps to keep minimize the amount of debugging, and lessens the code management.
-Note- Also if you looked carefully at my functions where I'm reading in the data from the file; you will not see while( !file.eof() )! This is bad code practice! The best way is to either use std::getline(...) or the stream << operators within a while loop to read in the data.
Related
I'm currently working with std::fstream and have the following class:
(Note that the file is opened on constructor and should stay open during all read / write operation, only close when destructed).
MyFileClass
{
public:
MyFileClass( const std::string& file_name ) { m_file.open( file_name ) };
~MyFileClass() { m_file.close() };
bool read( std::string& content );
bool write( std::string& data );
private:
std::fstream m_file;
}
Now I have some sample code:
MyFileClass sample_file;
sample_file.write("123456");
sample_file.write("abc");
Result will be "abc456" because when the stream is open and we're writing with truncate mode it will always write on top of what's currently in there.
What I would like to have is to clean up everytime before we write so at the end I'll have only what's newest in there, in this case "abc".
And the current design is if the file is not there, it will be created only on write, but not on read (read will return error code if file is not present).
My write function is:
bool
MyFileClass::write( const std::string& data )
{
m_file.seekg( 0 );
if ( !m_file.fail( ) )
{
m_file << data << std::flush;
}
return m_file.fail( ) ? true : false;
}
Is there any way to clear the current content of the file before flushing the data?
to be able to write to the end of file use flag ios::app instead.
your code is full of mistakes:
1- you also trying to write a class string to file which is not correct. if you want to do so use serialization. in your case just convert it to constant character string.
2- your write and read functions defined to take a reference to string but you pass by value! (passing "12345" and "abc")
3- why you seek input pointer? (seekg)? as long as you are trying to write??!!
you may meant seekp() seeking output pointer; even in this case for what reason to do so? if you want to append text to the end use ios::app at the opening file.
if you want to clearing the content at any write operation then you should have two file streams one for reading and other for writing so the one for writing uses flag ios::out | ios::trunc. because ios::trunc does nothing in reading/writing mode.
4- if you really want to pass by reference then you must declare string objects in main passing to them values ("abc" and "12345" for each one) then pass these strings to write and read not the values.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class MyFileClass
{
public:
MyFileClass( std::string file_name );
~MyFileClass() { m_file.close(); }
bool read ( std::string content );
bool write( std::string data );
private:
std::fstream m_file;
};
MyFileClass::MyFileClass( std::string file_name )
{
m_file.open( file_name.c_str(), ios::out | ios::in | ios::app);
if(!m_file)
cout << "Failed to open file!" << endl;
}
bool MyFileClass::write( const std::string data )
{
if ( !m_file.fail( ) )
{
m_file << data.c_str() << std::flush;
}
return m_file.fail( ) ? true : false;
}
int main()
{
std::string sFile = "data.dat";
MyFileClass sample_file(sFile);
sample_file.write("123456");
sample_file.write("abc");
return 0;
}
I am quite new to C++ and I have a txt file with data which looks something like this:
test:123:lock
qwerty:4321:unlock
asdf:12:lock
Is it possible for me to read the data line by line into a variable / array using ":" as the delimiter?
I tried doing something like:
while(!myfile.eof()) {
for (int i = 0; i < 3; i++) {
getline(myfile,UserN[i],':');
}
}
What I want to achieve is to store the data of the first line into the UserN[0], UserN[1], and UserN[2]. And when it start reading the second line, the data on the second line will replace the value in UserN[0], UserN[1], and UserN[2]. Thanks in advance!
Read the line first, then tokenize it with std::stringstream:
#include <sstream>
...
std::string line;
while(std::getline(myfile, line)) { // cache the line
std::istringstream tokenizer(line);
std::getline(tokenizer, UserN[0], ':'); // then get the tokens from it
std::getline(tokenizer, UserN[1], ':');
std::getline(tokenizer, UserN[2]); // last token: get the remainder
// of the line.
if(tokenizer) {
// success!
} else {
// There were fewer than two colons in the line
}
}
In essence, std::istringstream wraps a string in a stream interface -- the resulting stream behaves (roughly) like a file with the same contents as the string with which it was built. It is then possible to use >> or getline or anything else that you could use on files or std::cin or other input streams with it, and here we use it to take the string apart into the tokens you require.
You can do this simply with
ifstream myfile( "aFile.txt" );
// .. check whether the file is open: if( !myfile.is_oppen() ) error
for( string userN[3]
; getline( getline( getline( myfile >> ws, userN[0], ':' ), userN[1], ':' ), userN[2] ); )
{
// userN[0..2] is read correctly
}
or in a more elegant way, perhaps more suitable to Your requirements. I assume, that the second text is always a number and the third text is either 'lock' or 'unlock' or something else like an enum.
enum class LockState
{
lock, unlock
};
// -- reading a LockState
// please consider, that behind the text must follow a white space character (Space, LF, ..)
std::istream& operator>>(std::istream& in, LockState& s)
{
std::string word;
if( in >> word )
{
if( word == "lock" )
s = LockState::lock;
else if( word == "unlock" )
s = LockState::unlock;
else
in.setstate( std::ios_base::failbit );
}
return in;
}
struct Entry // change the name 'Entry' of the struct suitable for Your requirements
{
std::string someText;
int aNr;
LockState lockState;
};
// -- function to read an 'Entry'-object
std::istream& operator>>(std::istream& in, Entry& e)
{
char colon;
if( getline( in >> std::ws, e.someText, ':' ) >> e.aNr >> colon
&& colon != ':' )
in.setstate( std::ios_base::failbit );
else
in >> e.lockState;
return in;
}
and later in Your main-program
ifstream myfile( "aFile.txt" );
// .. check whether the file is open: if( !myfile.is_oppen() ) error
for( Entry e; myfile >> e; )
{
// use here the Entry-object 'e'
}
if( myfile.eof() )
cout << "Ok - You read the file till the end" << endl;
Avoid trouble here and use the split function from Boost:
#include <fstream>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
// ...
// Read file and throw exception on error.
std::ifstream infile;
infile.open(file_name);
std::string line;
while (std::getline(infile, line))
{
// Strip of the comments.
std::vector<std::string> strings;
boost::split(strings, line, boost::is_any_of(":"));
// You have now a vector of strings, which you can process...
}
Hey is it possible to have a text file which its contents are:
Weapon Name: Katana
Damage: 20
Weight: 6
Is it possible to assign these bits of information into member variables of a weapons class?.
So that when i call getWeaponName in my main i will get Katana?
I was looking around google and i can get the whole text file input but its not assigned to any variable.
The code i have so far is:
Weapons :: Weapons()
{
this->weaponName = "";
this->damage = 0;
this->weight = 0;
}
Weapons :: Weapons(string weaponName,int damage,int weight)
{
this->weaponName = weaponName;
this->damage = damage;
this->weight = weight;
}
void Weapons :: getWeapon()
{
ifstream myfile ("Weapons\\Katana.txt");
string line;
if (myfile.is_open())
{
while (myfile.good())
{
getline (myfile,weaponName,'\t');//This line gets the entire text file.
//getline (myfile,damage,'\t');
//getline (myfile,weight,'\t');
//myfile >> weaponName;
//myfile >> damage;
//myfile >> weight;
cout << weaponName<< "\n";
}
myfile.close();
}
else
{
cout << "Unable to open file";
}
}
Thanks in advance.
Change
getline (myfile, weaponName, '\t');
to
getline (myfile, weaponName);
What your version is doing is telling getline to grab everything in the file, up to a tab character, and I'm guessing you don't have any tab characters. The version I'm recommending - with no delimiter specified - will get characters up to a newline. So it should read in Weapon Name: Katana.
Then you still need to extract "Katana". Assuming your input file has a very fixed format, you can simply do something like
weaponName = weaponName.substr(weaponName.find_first_of(':') + 2);
This will take the substring starting at the position 2 after the ':'.
Edit
Using weaponName is not exactly proper for your getline statement. weaponName is a string, but at that point, you're just looking for a line. You already have the proper variables in place in getWeapon(). We just need to use them:
void Weapons :: getWeapon()
{
ifstream myfile ("Weapons\\Katana.txt");
string line;
string number;
if (myfile.is_open())
{
while (myfile.good())
{
getline (myfile,line);
weaponName = line.substr(line.find_first_of(':') + 2);
getline (myfile,line);
number = line.substr(line.find_first_of(':') + 2);
damage = atoi(number.c_str());
getline (myfile,line);
number = line.substr(line.find_first_of(':') + 2);
weight = atoi(number.c_str());
cout << weaponName<< "\n";
}
myfile.close();
}
else
{
cout << "Unable to open file";
}
}
Note: you'll need to #include <stdlib.h> for atoi to work.
Honestly, this still isn't very robust. Others have offered you better solutions for looking at the input to see what the data is, and reading and storing all your data, but this should show you the very basics.
You will need to parse each line of your file. So, change the function
getline(myfile, weaponName, '\t');
to
getline(myfile, weaponName);
and parse result.
Do something like that:
#include <cstdio>
#include <string>
using namespace std;
int main()
{
string line = "Weapon Name: Katana";
int pos = line.find(':');
string weaponName;
if ( line.substr(0, pos) == "Weapon Name")
weaponName = line.substr(pos+1, line.npos);
printf("%s\n", weaponName.c_str());
}
First, you need/want to distinguish between "weapons" (plural) and a single weapon. To make much sense, each individual weapon has the characteristics you're reading (name, weight, damage). So, weapons will be a collection of individual weapon objects, each of which has the characteristics.
Based on that, we can attempt to write some meaningful code:
class weapon {
std::string name;
int damage;
int weight;
public:
std::string get_name() { return name; }
Now, we want a weapon to be able to "reconstitute" itself from data stored in a file. Note, however, that right now we're writing a weapon class, so we're only going to deal with one weapon, not a whole collection of them:
friend std::istream &operator>>(std::istream &is, weapon &w) {
std::ignore(is, 1024, ':'); // ignore the "Weapon Name:" header
std::getline(is, w.name);
std::ignore(is, 1024, ':'); // ignore the "Damage:" header
is >> w.damage;
std::ignore(is, 1024, ':'); // ignore the "Weight:" header
is >> w.weight;
return is;
}
Though we don't need it just yet, let's create a matching function to write out a weapon in the correct format as well:
std::ostream &operator<<(std::ostream &os, weapon const &w) {
return os << "Weapon Name: " << w.name << "\n"
<< "Damage: " << w.damage << "\n"
<< "Weight: " << w.weight << "\n";
}
With that, we can read the data for a single weapon. Then we need some way to store multiple weapons. Lacking a reason to do otherwise, our first choice for that is normally an std::vector. If we want to fill that with the data from a file, we can do it something like this:
// open a file of all the weapons data:
std::ifstream in("weapons.txt");
// initialize the vector from the data in the file:
std::vector<weapon> weapons((std::istream_iterator<weapon>(in)),
std::istream_iterator<weapon>());
With this in place we can (for example) list all the weapons (here we're going to use the "operator<<" we defined above):
std::copy(weapons.begin(), weapons.end(),
std::ostream_iterator<weapon>(std::cout, "\n"));
If we want an abbreviated list with just the name of each weapon, we can do something like this:
for (auto const &w : weapons)
std::cout << w.get_name() << "\n";
Your format looks like a variant of a typical .ini file. There
are lots of parsers around for that, if you can modify the
format to make it conform. That would be by far the easiest
solution. Otherwise: how are the various weapons separated in
the file? Is it by an empty line, or is it because the first
entry is always "Weapon Name"? In the first case, I would use
something like the following to read the file (in a free
function, not as a member):
std::auto_ptr<Weapon> currentWeapon;
Line line;
int lineNumber = 0;
while ( source >> line ) {
++ lineNumber;
if ( line.empty() ) {
if ( currentWeapon.get() != NULL ) {
weaponCollection.insert( currentWeapon );
}
currentWeapon.release();
} else {
Line::const_iterator pivot
= std::find( line.begin(), line.end(), ':' );
if ( pivot == line.end() ) {
// Output error message, using lineNumber...
} else {
if ( currentWeapon.get() == NULL ) {
currentWeapon.reset( new Weapon );
}
std::string key( strip( std::string( line.begin(), pivot ) ) );
std::string value( strip( std::string( pivot + 1, line.end() ) ) );
if ( key == "WeaponName" ) {
currentWeapon->weaponName = value;
} else if ( key == "Damage" ) {
currentWeapon->damage = asInt( value );
} else if ( key == "Weight" ) {
currentWeapon->weight = asInt( value );
} // ...
else {
// output error message...
}
}
}
}
Line is a class in my toolbox, that I use a lot for this sort
of thing. It's basically:
class Line : private std::string
{
public:
typedef std::string::iterator iterator;
// ...
using std::string::empty;
using std::string::begin;
// ...
};
The only difference between it and std::string is that its
operator>> calls std::getline, then "cleans up" the results,
by removing any trailing white space (including a possible
'\r', because the file was written under Windows, but I'm
reading it under Unix); in this case, it might be useful to make
it also remove any leading white space. (Mine also has
a manipulator which sets a comment character; if this is set, it
removes any text from this character to the end of the line,
before trimming trailing whitespace.
(From experience: do provide some sort of facility for commenting
in the file. You'll regret it if you don't.)
And asInt is, of course:
int
asInt( std::string const& original )
{
std::istringstream s( original );
int results;
s >> results >> std::ws;
if ( !s || s.get() != EOF ) {
// Error...
}
return results;
}
Again, something that you should have in your toolbox already.
If the key to a new weapon is the "Weapon Name" attribute,
skip the empty line business (or treat empty lines as comments),
and store any existing Weapon and create the new one in the
handling for "Weapon Name".
If you throw on error, above, you'll need to use a try...catch
block to output the error and continue. You might also want to
mark that there was an error somewhere, and abort the game if
so.
here's what I need to do. I have a string in C++. For every line in the string, I need to append a few characters (like ">> ") to the beginning of the line. What I am struggling with is a good way to split the string around newlines, iterate through the elements appending the characters, and then rejoin the string together. I've seen a few ideas, such as strtok(), but I was hoping c++ strings would have something a little more elegant.
Here's a straight-forward solution. Maybe not the most efficient, but unless this is hot code or the string is huge, it should do fine. We suppose that your input string is called input:
#include <string>
#include <sstream>
std::string result;
std::istringstream iss(input);
for (std::string line; std::getline(iss, line); )
{
result += ">> " + line + "\n";
}
// now use "result"
A more functional approach would be to use a getline-based iterator as shown in this answer and then use that with std::transform for transforming all input lines, like this:
std::string transmogrify( const std::string &s ) {
struct Local {
static std::string indentLine( const std::string &s ) {
return ">> " + s;
}
};
std::istringstream input( s );
std::ostringstream output;
std::transform( std::istream_iterator<line>( input ),
std::istream_iterator<line>(),
std::ostream_iterator<std::string>( output, "\n" ),
Local::indentLine );
return output.str();
}
The indentLine helper actually indents the line, the newlines are inserted by the ostream_iterator.
If the data in your string is basically like a file, try using std::stringstream.
std::istringstream lines( string_of_lines );
std::ostringstream indented_lines;
std::string one_line;
while ( getline( lines, one_line ) ) {
indented_lines << ">> " << one_line << '\n';
}
std::cout << indented_lines.str();
You can wrap it in a stringstream and use std::getline to extract a line at a time:
std::string transmogrify(std::string const & in) {
std::istringstream ss(in);
std::string line, out;
while (getline(ss, line)) {
out += ">> ";
out += line;
out += '\n';
}
return out;
}
Here is a sample program that uses stringstream. The goal is to accept lines from the user(standard input) and print each word in a separate line.
int main()
{
std::istringstream currentline;
std::string eachword;
std::string line;
// Accept line from the standard input till EOF is reached
while ( std::getline(std::cin,line) )
{
currentline.str(line); // Convert the input to stringstream
while ( currentline >> eachword ) // Convert from the entire line to individual word
{
std::cout << eachword << std::endl;
}
currentline.clear();
}
return 0;
}
I'm wondering, is there a way , I can avoid the intermediate string variable(object), line and directly store the user input to the currentline (istringstream object).
Note:
I know, the following solution already.
while ( std::cin >> eachword)
{
std::cout << eachword << std::endl;
}
std::getline needs a string reference argument, and that's where it places the line it has obtained, so of course you can't avoid passing such an argument (and still use that function). You could elegantly encapsulate the construct, if you need it often -- e.g.:
bool getline(std::istream& i, std::istringstream& current)
{
std::string line;
if ( std::getline(i, line) ) {
current.str(line);
return true;
}
return false;
}
If you want to simplify the first solution,
while ( currentline(line) >> eachword )
I assume you want to not use an intermediate object to prevent unnecessary copying?
You can achieve the same affect by explicitly setting the stream buffers buffer area.
int main()
{
std::string line;
std::istringstream currentline;
std::string eachword;
// Accept line from the standard input till EOF is reached
while ( std::getline(std::cin,line) )
{
// Set the buffer without copying.
currentline.clear();
currentline.rdbuf()->pubsetbuf(&line[0], line.length() );
while ( currentline >> eachword )
{
std::cout << eachword << std::endl;
}
}
return 0;
}
Because of the order of destruction. You just need to make sure the istringstream is destroyed before the object you are using as a buffer. So you need to re-arrange the declarations at the top of main() to make sure that line is created first and thus will be destroyed last (otherwise the destructor of the istringstream has the potential for accessing the memory of a free'ed object.