Bit of a long post, but I'm having difficulty trying to make this program read through the entirety of a text file instead of what it seems like just the first line.
// return true only if the code is valid
bool PriceList::isValid(string code) const {
ifstream myfile;
string line;
myfile.open("pricelist.txt");
if (myfile.is_open()) {
while (getline(myfile,line))
{
if (line.find(code) != string::npos)
{
cout << line <<endl;
return true;
}
else
{
cout << line <<endl;
return false;
}
}
}
else
throw invalid_argument("Could not open file");
}
The goal of this part of this program is to determine whether a product bar-code in a text file exists or not. Here is part of the text file (pricelist.txt) that was given to us (categories from left to right are name, barcode, price and taxable):
Soda_can 1035851680 2.29 1
Red_Cabbage 1163115674 1.99 0
Tomato_Sauce 1195034963 0.29 0
Tuna_chunky_in_water 1304271912 0.59 0
Salad_Dressing 1352230364 1.39 0
Here is the code snippet of the main class that tests this function. The addEntry lines calls the addEntry function I already coded to add each item to the end of the text file (we are mainly focusing on the bar-code data values i.e. 1000, 2000). The testAnswer lines calls a function to state whether or not the function is running properly. The first data value is the title of the test, the second is the variable to be tested and the third is the value the function should result in. So for example the first testAnswer line is testing whether or not the barcode 1000 is in the text file and the result is suppose to be true:
{ PriceList priceList;
priceList.addEntry("Apples", "1000", 1.99, false);
priceList.addEntry("Bananas", "2000", 0.99, false);
testAnswer("PriceList isValid1", priceList.isValid("1000"), true);
testAnswer("PriceList isValid2", priceList.isValid("19"), false); }
So as I've said I am having trouble making the program read all of the text file. It only seems to be reading the first line, the Soda_can one and it causes both testAnswer functions to result in false. Here is the current output:
Soda_can 1035851680 2.29 1
FAILED PriceList isValid1: expected 1 but received 0
Soda_can 1035851680 2.29 1
PASSED PriceList isValid2: expected and received 0
If I manually put the Apples and Bananas data values at the very top it results in correct values but obviously I want to make it work for any item anywhere on the text file. I've spent hours trying to solve this and this is the best I've got right now. I'd greatly appreciate any help in this and I thank you for reading all of this if you have. Let me know if any of my explanations are unclear.
Inside your while clause you are calling return in both the if and else sections. This will end processing completely for the function. There is no way for the while clause to reach the second iteration.
The actual problem is that your program returns irrespective of result of code checking if (line.find(code) != string::npos).
Probably, you would like to continue search/reading through file if code is not found and stop reading file once code is found. This should continue till EOF is reached.
You can use an additional variable like bFound to store result and return it. Like in code below:
bool bFound = false;
while (getline(myfile,line))
{
if (line.find(code) != string::npos)
{
cout << line <<endl;
bFound = true;
break; //Record found break from loop
}
else
{
cout << line <<endl;
continue; //Record not found. Continue loop.
}
}
return bFound;
Related
I am making a device that moves back and fourth and needs to store its last position so that upon power up, the last stored value can be grabbed from the last line of the file on an SD card, and it can resume operation. This file will then be destroyed and re-written. For this particular application homing and other methods can not be used because it must start in the spot it last was. Due to position tracking via encoder, there is no positional memory otherwise.The file is setup to be a single data column seperated by commas.
Currently I am successfully writing to the SD card as position changes, and reading the entire file to be printed on the Serial monitor. However, I really only need the last value. The length of the file will always be different do to system operation.
I have read a lot of different solutions but none of them seem to work for my application.
I can read the entire file using:
void read_file() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
Serial.println("test8.txt:");
// read from the file until there's nothing else in it:
// read from the file until there's nothing else in it:
while (myFile.available()) {
String a = "";
for (int i = 0; i < 9; ++i)
{
int j;
char temp = myFile.read();
if (temp != ',' && temp != '\r')
{ //a=temp;
a += temp;
}
else if (temp == ',' || temp == '\r') {
j = a.toInt();
// Serial.println(a);
Serial.println(j);
break;
}
}
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test8.txt");
}
}
This gives me a stream of the values separated by 0 like this:
20050
0
20071
0
20092
0
20113
0
20133
0
Ideally I just need 20133 to be grabbed and stored as an int.
I have also tried:
void read_file_3() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
Serial.println("test8.txt:");
// read from the file until there's nothing else in it:
Serial.println(myFile.seek(myFile.size()));
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
This only returns "1", which does not make any sense to me.
Update:
I have found a sketch that does what I want, however it is very slow due to the use of string class. Per post #6 here: https://forum.arduino.cc/index.php?topic=379209.0
This does grab the last stored value, however it takes quite awhile as the file gets bigger, and may blow up memory.
How could this be done without the string class?
void read_file() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
while (myFile.available())
{
String line_str = myFile.readStringUntil(','); // string lavue reading from the stream - from , to , (coma to comma)
int line = line_str.toInt();
if (line != 0) // checking for the last NON-Zero value
{
line2 = line; // this really does the trick
}
// Serial.print(line2);
// delay(100);
}
Serial.print("Last line = ");
Serial.print(line2);
// close the file:
myFile.close();
// SD.remove("test3.txt");
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
}
}
Any help would be greatly appreciated!
seek returns true if it succesffuly goes to that position and false if it does not find anything there, like for instance if the file isn't that big. It does not give you the value at that position. That's why you see a 1, seek is returning true that it was able to go to the position (myFile.size()) and that's what you're printing.
Beyond that, you don't want to go to the end of the file, that would be after your number. You want to go to a position 5 characters before the end of the file if your number is 5 digits long.
Either way, once you seek that position, then you still need to use read just like you did in your first code to actually read the number. seek doesn't do that, it just takes you to that position in the file.
EDIT: Since you edited the post, I'll edit the answer to go along. You're going backwards. You had it right the first time. Use the same read method you started with, just seek the end of the file before you start reading so you don't have to read all the way through. You almost had it. The only thing you did wrong the first time was printing what you got back from seek instead of seeking the right position and then reading the file.
That thing you looked up with the String class is going backward from where you were. Forget you ever saw that. It's doing the same thing you were already doing in the first place only it's also wasting a lot of memory and code space in the process.
Use your original code and just add a seek to skip to the end of the file.
This assumes that it's always a 5 digit number. If not then you may need a little bit of tweaking:
void read_file() {
// open the file for reading:
myFile = SD.open("test8.txt");
if (myFile) {
Serial.println("test8.txt:");
/// ADDED THIS ONE LINE TO SKIP MOST OF THE FILE************
myFile.seek(myFile.size() - 5);
// read from the file until there's nothing else in it:
// read from the file until there's nothing else in it:
while (myFile.available()) {
String a = "";
for (int i = 0; i < 9; ++i)
{
int j;
char temp = myFile.read();
if (temp != ',' && temp != '\r')
{ //a=temp;
a += temp;
}
else if (temp == ',' || temp == '\r') {
j = a.toInt();
// Serial.println(a);
Serial.println(j);
break;
}
}
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test8.txt");
}
}
See, all I've done is take your original function and add a line to seek the end to it.
So I am working on a model loader in my DirectX 11 program, and I ran into what I think is a unique issue. So I spent a bit of time looking for a solution to this, but failed to do so. My problem is that in my file that has the texture path and list of vertices, I want to be able to pick out certain parts, and remove some aswell. Below my example file for a texture-less triangle:
T:0$
(0, 5, 0)
(5, 0, 0)
(-5, 0, 0)
^ THIS IS OLD, LOOK AT EDIT BELOW ^
Let me explain what's happening here. First, the "T:___" is my file path to the texture. I have set this to "0" because I am not using a texture. The "$" after the "T:0" is my program's mark for the end of the file path and beginning of the vertices.
Now, here is what I need my program to do.
1. Read the file until the character "$" has been reached. Then erase the first two characters(the "T:") and the "$" if it has been added as well. Finally put the remaining text into a string called TextureData. P.S. Don't erase the "T:" from the file, just my string(The file needs to stay untouched).
2. Put the remaining text(vertices) into a temporary string called VertexData, then maybe remove the parenthesis..? I would like to know how to do this, but maybe not use it at the moment.
I hope I made myself and my issue clear enough.
Thanks in advance.
--- IMPORTANT EDIT ---
I have changed my format a little bit, I looked at a .obj file and decided that that would be easier to do. My texture and vertex file now looks like this:
T:0$
v 0 5 0
v 5 0 0
v -5 0 0
--- END OF EDIT ---
This code here is what I have as a basis:
Model loading function:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open())
{
ReadLine(1); // Function not fully working, need to improve
// Load vertices and texture into strings
TVFReader.close();
return true;
}
else
{
return false;
}
}
ReadLine function(made just to organize my code and jump to a certain line and retrieve that lines data and put into a string, the cutting and modifying of the string needs to be back in the main function):
string ReadLine(int character)
{
return lineData; // I know this doesn't work, just don't know what to return?
}
Honestly with this ReadLine function, I have no idea what I'm doing. I was just making some kind of frame to show you how I would prefer the code to be organized.
Once again, thank you.
This is how I would do it:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open()) {
stringstream buffer;
buffer << TVFReader.rdbuf();
string texture_string = buffer.str();
texture_string = texture_string.substr(2, texture_string.length());
// Removes T:
texture_string.erase(texture_string.begin() + texture_string.find("$"));
// Removes the first occurrence of $
std::cout << texture_string;
/* This will print out:
* 0
* v 0 5 0
* v 5 0 0
* v -5 0 0
*/
TVFReader.close();
return true;
}
else
{
return false;
}
}
This doesn't use a ReadLine function because it loads the entire string at once. Since I'm only manipulating that string at the beginning, I would rather have the parsing and pruning logic happen in one place.
If you must iterate line-by-line, there's already a way to do that using getline:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open()) {
string line;
while(getline(TVFReader, line))
{
// Do stuff with each line
}
TVFReader.close();
return true;
}
else
{
return false;
}
}
Note that these lines don't have end-of-line characters in them.
Edit:
As per OP's comments, here's how you would split up the parsed file into separate strings:
bool LoadTVF(string FP)
{
ifstream TVFReader;
TVFReader.open(FP);
if (TVFReader.is_open()) {
string header;
getline(TVFReader, header);
header = header.substr(2, header.length());
// Removes T:
header.erase(header.begin() + header.find("$"));
// Removes the first occurrence of $
std::cout << header << std::endl;
// This should print 0, which is the string between T: and $
stringstream buffer;
string line;
while (getline(TVFReader, line))
{
line = line.substr(2, line.length());
// Removes the starting "v "
buffer << line << std::endl;
// We need the end-of-line here because its been stripped
}
string texture_string = buffer.str();
std::cout << texture_string;
/* This will print out:
* 0 5 0
* 5 0 0
* -5 0 0
*/
TVFReader.close();
return true;
}
else
{
return false;
}
}
A few notes I'd like to make here. If you're in control of the way your file is structured, you shouldn't need the $ character to indicate the end of that line. The fact that there's a newline should be indication enough of that. Additionally, since every line after that represents a vector in a matrix, I don't think the v is necessary either. Finally, the T: may also be unnecessary if your files are named appropriately, like "test.texture". That way you know that the file you're reading is a texture.
This would reduce the complexity of parsing these files, and as a bonus, reduces the storage sizes for these as well.
So far it looks like you can read your file line by line using getline and then extract necessary information: figure out "line type" by checking first character, then depending on type either get substring with path or split line into chunks and convert them to floats.
Later when/if your file format becomes more complicated you might want to write a full blown parser probably using some sort of parser generator.
I'm fairly new to C++ so please forgive me if my terminology or methodology isn't correct.
I'm trying to write a simple program that:
Opens two input files ("infileicd" and "infilesel").
Opens a single output file "list.txt".
Compares "infilesel" to "infileicd" line by line.
If a line from "infilesel" is found in "infileicd", it writes that line from "infileicd" to "list.txt", effectively making a separate log file.
I am using the getline() function to do this but have run into trouble when trying to compare each file line. I think it might be easier if I could use only the substring of interest to use as a comparison.
The problem is that there are multiple words within the entire getline string and I am only really interested in the second one. Here are two examples:
"1529 nic1_mau_op_mode_3 "8664afm007-01" "1" OUTPUT 1 0 LOGICAL 4 4136"
"1523 pilot_mfd_only_sel "8664afm003-02" "1" OUTPUT 1 0 LOGICAL 4 4112"
"nic1_mau_op_mode_3" and "pilot_mfd_only_sel" are the only substrings of interest.
It would make it a lot easier if I could only use that second substring to compare but I don't know how to extract it specifically from the getline() function. I haven't found anything suggesting it is impossible to do this, but if it is impossible, what would be an alternative method for extracting that substring?
This is a personal project so I'm under no time contstraints.
Any assistance is greatly apprecated in advance. Here is my code (so far):
int main()
{
//Open the file to write the selected variables to.
ofstream writer("list.txt");
//Open the selected variabels file to be read.
ifstream infilesel;
infilesel.open("varsel.txt");
//Open the icd file to be read.
ifstream infileicd;
infileicd.open("aic_fdk_host.txt");
//Check icd file for errors.
if (infileicd.fail()){
cerr << "Error opening icd.\n" << endl;
return 1;
}
else {
cout << "The icd file has been opened.\n";
}
//Check selected variables file for errors.
if (infilesel.fail()){
cerr << "Error opening selection file.\n" << endl;
return 1;
}
else {
cout << "The selection file has been opened.\n";
}
//Read each infile and copy contents of icd file to the list file.
string namesel;
string nameicd;
while(!infileicd.eof()){
getline(infileicd, nameicd);
getline(infilesel, namesel);
if (nameicd != namesel){ //This is where I would like to extract and compare the two specific strings
infileicd; //Skip to next line if not the same
} else {
writer << nameicd << namesel << endl;
}
}
writer.close();
infilesel.close();
infileicd.close();
return 0;
}
So, based on what we discussed in the comments, you just need to toss the stuff you don't want. So try this:
string namesel;
string nameicd;
string junk;
while(!infileicd.eof()){
// Get the first section, which we'll ignore
getline(infileicd, junk, ' ');
getline(infilesel, junk, ' ');
// Get the real data
getline(infileicd, nameicd, ' ');
getline(infilesel, namesel, ' ');
// Get the rest of the line, which we'll ignore
getline(infileicd, junk);
getline(infilesel, junk);
Basically, getline takes a delimiter, which by default is a newline. By setting it as a space the first time, you get rid of the first junk section, using the same method, you get the part you want, and then the final portion goes to the end of the line, also ignoring it.
I'm trying to implement a function in C++ that searches for a string in a file, then prints the the line containing the string AND X subsequent lines.
I have the following code that works for finding the string and printing the line, but I can't get it to work to print the lines under the line with the string.
void repturno(void){
system("cls");
string codemp, line,output;
bool found = false;
ifstream myfile ("Pacientes.csv");
//captures the string that i need to look for, in this case an employee code
cout<<"\nBienvenida enfermera en turno, por favor introduzca su codigo"<<endl;
cin.ignore();
getline(cin,codemp);
system("cls");
/*reads the file and searches for the string, then prints the whole line, then searches again for
the string and if finds it, will print the whole line again*/
while (getline(myfile, line)) {
if (line.find(codemp) != string::npos) {
cout<<line<<endl;
getline(myfile,line);
found = true;
}
}
//using this to check if the code was working, and verifying if the string was found or not :P
if( found == false){
cout <<"No se encontro la cadena"<< endl;
}
system("pause");
return menu();
}
Here are my problems:
I can't get the cursor to go down to the next line and print it (need to do this 4 times), everything just gets messy and I end up with the same string and its line printed all over the screen many times.
I can't save all the data pertinent to each employee in a single line (it would solve the problem in this function, but would create other problems); there's too much info for each employee, way more than the 80 characters allowed in the console and it messes up other parts of my program where I have to display the information on the screen and it looks really bad when i print it, even when trying to use gotoxy or '\t' for proper spacing. I had to save the data line by line, each category in a different line, so I can properly display it in other parts of my programs.
I came with 2 solutions in pseudo-code, but i don't know how to translate them into C++ sentences:
NOTE: the search loop is already defined in the code above, so won't break it down step by step in the pseudo-code, will refer to it as SEARCH LOOP
Pseudo-code #1
START SEARCH LOOP
SEARCH for the desired string(`codemp`) line by line
IF string is found
PRINTLINE containing the STRING
MOVE CURSOR one line below
PRINT the entire line
MOVE CURSOR one line below
PRINT the entire line
MOVE CURSOR one line below
PRINT the entire line
MOVE CURSOR one line below
PRINT the entire line
Move CURSOR one line below
RESTART SEARCH LOOP in the current line
IF NO matching string is found AND EOF
END IF
END SEARCH LOOP
END PROGRAM
Psuedo-code #2
START SEARCH LOOP
SEARCH for the desired string(codemp) line by line
IF string is found
PRINTLINE containing the string
PRINTING LOOP
MOVE CURSOR one line below
ASSIGN line to a temp_string
IF temp_string = "\n\n"
//each employee is separated by 2 whitelines after its info
DO NOTHING
ELSE
PRINTLINE temp_string
RESTART PRINTING LOOP
END IF
END PRINTING LOOP
RESTART SEARCH LOOP
IF NO string is found AND EOF
END SEARCH LOOP
END PROGRAM
I hope this is clear and detailed enough. That's what I want to do with my program, but I can't find a way to translate it into c++ sentences. Please let me know if there's a better, or more efficient way to do what I want to do.
I didn't test it but that should work :
while( getline(myfile,line) )
{
if( line.find( codemp ) != string::npos )
{
cout << line << endl;
for( int i = 0; i < nbLines && getline( myfile, line ); ++i )
cout << line << endl;
}
}
It iterate the file with a while and when it find the string it print "nbLines" other lines"
Since what you have is working for you, let's look at that first. Here I've xtracted just the relevant portion, with a small addition. All we need to do is print a four more lines in the case that the code was found, so:
while (getline(myfile, line)) {
if (line.find(codemp) != string::npos) {
cout<<line<<endl;
getline(myfile,line);
found = true;
for (int i=4; i; --i) { <== new code begins
cout << line << '\n';
getline(myfile, line);
} <== new code ends
}
}
Simple as that.
i have a text file with data like the following:
name
weight
groupcode
name
weight
groupcode
name
weight
groupcode
now i want write the data of all persons into a output file till the maximum weight of 10000 kg is reached.
currently i have this:
void loadData(){
ifstream readFile( "inFile.txt" );
if( !readFile.is_open() )
{
cout << "Cannot open file" << endl;
}
else
{
cout << "Open file" << endl;
}
char row[30]; // max length of a value
while(readFile.getline (row, 50))
{
cout << row << endl;
// how can i store the data into a list and also calculating the total weight?
}
readFile.close();
}
i work with visual studio 2010 professional!
because i am a c++ beginner there could be is a better way! i am open for any idea's and suggestions
thanks in advance!
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <limits>
struct entry
{
entry()
: weight()
{ }
std::string name;
int weight; // kg
std::string group_code;
};
// content of data.txt
// (without leading space)
//
// John
// 80
// Wrestler
//
// Joe
// 75
// Cowboy
int main()
{
std::ifstream stream("data.txt");
if (stream)
{
std::vector<entry> entries;
const int limit_total_weight = 10000; // kg
int total_weight = 0; // kg
entry current;
while (std::getline(stream, current.name) &&
stream >> current.weight &&
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n') && // skip the rest of the line containing the weight
std::getline(stream, current.group_code))
{
entries.push_back(current);
total_weight += current.weight;
if (total_weight > limit_total_weight)
{
break;
}
// ignore empty line
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
else
{
std::cerr << "could not open the file" << std::endl;
}
}
Edit: Since you wannt to write the entries to a file, just stream out the entries instead of storing them in the vector. And of course you could overload the operator >> and operator << for the entry type.
Well here's a clue. Do you see the mismatch between your code and your problem description? In your problem description you have the data in groups of four lines, name, weight, groupcode, and a blank line. But in your code you only read one line each time round your loop, you should read four lines each time round your loop. So something like this
char name[30];
char weight[30];
char groupcode[30];
char blank[30];
while (readFile.getline (name, 30) &&
readFile.getline (weight, 30) &&
readFile.getline (groupcode, 30) &&
readFile.getline (blank, 30))
{
// now do something with name, weight and groupcode
}
Not perfect by a long way, but hopefully will get you started on the right track. Remember the structure of your code should match the structure of your problem description.
Have two file pointers, try reading input file and keep writing to o/p file. Meanwhile have a counter and keep incrementing with weight. When weight >= 10k, break the loop. By then you will have required data in o/p file.
Use this link for list of I/O APIs:
http://msdn.microsoft.com/en-us/library/aa364232(v=VS.85).aspx
If you want to struggle through things to build a working program on your own, read this. If you'd rather learn by example and study a strong example of C++ input/output, I'd definitely suggest poring over Simon's code.
First things first: You created a row buffer with 30 characters when you wrote, "char row[30];"
In the next line, you should change the readFile.getline(row, 50) call to readFile.getline(row, 30). Otherwise, it will try to read in 50 characters, and if someone has a name longer than 30, the memory past the buffer will become corrupted. So, that's a no-no. ;)
If you want to learn C++, I would strongly suggest that you use the standard library for I/O rather than the Microsoft-specific libraries that rplusg suggested. You're on the right track with ifstream and getline. If you want to learn pure C++, Simon has the right idea in his comment about switching out the character array for an std::string.
Anyway, john gave good advice about structuring your program around the problem description. As he said, you will want to read four lines with every iteration of the loop. When you read the weight line, you will want to find a way to get numerical output from it (if you're sticking with the character array, try http://www.cplusplus.com/reference/clibrary/cstdlib/atoi/, or try http://www.cplusplus.com/reference/clibrary/cstdlib/atof/ for non-whole numbers). Then you can add that to a running weight total. Each iteration, output data to a file as required, and once your weight total >= 10000, that's when you know to break out of the loop.
However, you might not want to use getline inside of your while condition at all: Since you have to use getline four times each loop iteration, you would either have to use something similar to Simon's code or store your results in four separate buffers if you did it that way (otherwise, you won't have time to read the weight and print out the line before the next line is read in!).
Instead, you can also structure the loop to be while(total <= 10000) or something similar. In that case, you can use four sets of if(readFile.getline(row, 30)) inside of the loop, and you'll be able to read in the weight and print things out in between each set. The loop will end automatically after the iteration that pushes the total weight over 10000...but you should also break out of it if you reach the end of the file, or you'll be stuck in a loop for all eternity. :p
Good luck!