How replace specific value from textfile using ofstream c++? - c++

Hi I am working an a vending machine and I want to update the quantity of the item by updating the text file. I been trying using ofstream and ifstream but is not working.
This is my text file.
Water:1:1.99:D1
Coke:4:2.79:D2
Milk:6:3.15:D3
Ham Sandwitch:9:4.50:L1
Lunchables:3:6.00:L2
Cereal:2:3.59:L3
M&M:8:1.75:C1
SourPatch:0:2.10:C2
Twix:6:2.99:C3
This is where vending machine checks the user input and where i want to update the file
void vendingWorking(Item &item) {
if(item.quantity == 0) {
cout << endl;
cout << "------------------------------------" << "\n";
cout << "" << item.name << " (OutStock)" << endl;
cout << "------------------------------------" << "\n";
}
else {
//Check if itemCode is same as product ID
if(itemCode == item.productId) {
//HERE I WANT TO UPDATE THE QUANTITY OF THE ITEM IF USER HAS PICKED ONE
//EXAMPLE: (Old) Water:2:2.50:D1 -> (New) Water:1:2.50:D1
//Message for user
cout << endl;
cout << "------------------------------------" << "\n";
cout << "" << item.name << ", $" << fixed << setprecision(2)<< item.price << " (InStock)" << "\n" ;
//Pass value to vector
tempBasket.push_back({item.name, item.price});
}
}
}

What you would like to do is:
Read the product content of the vending machine from a file
Modify data somehow
Write the product content of the vending machine to a file
How does modify somehow work? As you cannot change files online with arbitrary new data, you need to do like this:
Read file into memory --> operate on data in memory --> Save modified data in file
For the above there are 2 approaches.
Open file --> Read data --> Close file --> Modfiy data in memory --> Open file for output by overwrite original file --> Save Data --> Close file
Or, a little bit safer:
Open file --> Read data --> Close file --> Modfiy data in memory --> Open temporary file for output --> Save Data in temporary file --> Close temporary file --> If everything OK, delete original file --> rename temporary file to orignial file name
But the key is, to work on the data in memory.
You can also create "load" and "save" fucntions. So, at any time, after changing data in memory, you could "save" the modified data. With one of the above described methods.
Or, you could "load" your data in a constructor and "save" it in a destructor. Everything would then work automatically.
Regarding the "load" function. You need to read the source file line by line and then split the line into your needed data members. I have answered a question here, which describes 4 different methods on how to split a line. In the below given example, I use a std::regex based solution using std::regex_match. This will ensure that the data is in the expected format.
Please note that you should also overwrite the extractor and inserter operators >> and << for easier working with streams.
And last but not least, everything should be encapsulated in classes.
Please see a working and tested example code for a partial implemented vending machine functionality. In this code I am using C++17 features, like if with initializer. So, If you want to compile, then please enable C++17 for your compiler.
Additionally, this is just some code to illustrate the explanations above. There are 1 million solutions. In the end you need to come up with sometthing fitting the requirements.
#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <vector>
#include <regex>
#include <algorithm>
#include <numeric>
const std::regex re{ R"(^([^:]+):(\d+):(\d+\.\d+):([A-Z]+\d+))" };
class VendingMachine {
// Local definition of item struct
struct Item {
// Item attributes
std::string name{};
unsigned long quantity{};
double price{};
std::string productID{};
// Simple overwrite of extractor operator
friend std::istream& operator >> (std::istream& is, Item& it) {
// Read a complete line and check, if that worked
if (std::string line{}; std::getline(is, line)) {
// Check, if the input line, is in the expected format
if (std::smatch sm{}; std::regex_match(line, sm, re)) {
it.name = sm[1];
it.quantity = std::stoul(sm[2]);
it.price = std::stod(sm[3]);
it.productID = sm[4];
}
else std::cerr << "\n***Error while reading: '" << line << "'\n'";
}
return is;
}
// Simple overwrite of inserter operator
friend std::ostream& operator << (std::ostream& os, const Item& it) {
return os << it.name << ':' << it.quantity << ':' << it.price << ':' << it.productID;
}
};
// All products in vending machine
std::vector<Item> products{};
// Filename for saving and loading
std::string fileName{ "products.txt" };
public:
// Constructor and Destructor
// Constructor will load the data from a file
VendingMachine() { load(); }; // Default constructor
VendingMachine(const std::string& fn) : fileName(fn) { load(); }; // Constructor + file name
// Destructor will automatically save product file
~VendingMachine() { save(); };
// Simple overwrite of extractor operator
friend std::istream& operator >> (std::istream& is, VendingMachine& vm) {
// Delete all existing products
vm.products.clear();
// Copy all data from stream into internal structure
std::copy(std::istream_iterator<Item>(is), {}, std::back_inserter(vm.products));
return is;
}
// Simple overwrite of extractor operator
friend std::ostream& operator << (std::ostream& os, const VendingMachine& vm) {
// Copy all data to stream
std::copy(vm.products.begin(), vm.products.end(), std::ostream_iterator<Item>(os, "\n"));
return os;
}
// Load file from file
void load() {
// Open file and check, if it could be opened
if (std::ifstream ifs(fileName); ifs) {
// Use existing extractor operator
ifs >> *this;
}
else std::cerr << "\n***Error: Could not open file '" << fileName << "' for reading\n";
}
// Save products to file
void save() {
// Open file and check, if it could be opened
if (std::ofstream ofs(fileName); ofs) {
// Use existing inserter operator
ofs << *this;
}
else std::cerr << "\n***Error: Could not open file '" << fileName << "' for writing\n";
}
// Show the complete content of the vending machine. Even if one product category quantity is 0
void displayContent() {
// Some header line
std::cout << "\nNumber of selections in vending machine: " << products.size() << "\n\nProducts:\n\n";
// All Items wit their attributes
for (const Item& item : products)
std::cout << item.productID << "\t Quantity: " << item.quantity << "\t Price: " << item.price << "\t --> " << item.name << '\n';
}
// Select an item and the decrease quatnity
void getItem() {
// COunt the number of overall items in the vending maschine
const unsigned long overallItemQuantity = std::accumulate(products.begin(), products.end(), 0UL, [](size_t sum, const Item& it) {return sum + it.quantity; });
// If there are at all products in the machine and not all item quantity is 0
if (products.size() && overallItemQuantity > 0UL ) {
// Instruction from user
std::cout << "\n\nGet item\nPlease select from below list:\n\n";
// Show list of possible selections
for (const Item& item : products) {
if (item.quantity > 0UL) std::cout << item.productID << " \tPrice " << item.price << " \t--> " << item.name << '\n';
}
// Get user input. What item does the user want to have
std::cout << "\n\nPlease select product by typing the ID: ";
if (std::string id{}; std::getline(std::cin, id)) {
// FInd the selected item in the product list
if (std::vector<Item>::iterator iter{ std::find_if(products.begin(), products.end(),[&id](const Item& i) {return i.productID == id && i.quantity > 0UL; }) };iter != products.end())
// In my example I do not handle payment. Simply decrease quantity
--iter->quantity;
else
std::cerr << "\n\n***Error: Unknown product ID\n"; // Wrong input
}
}
else std::cerr << "\n\n***Error: Vending machine empty\n";
}
// Run the machine. Main menu and actions. At the moment kust get items without payment
// Needs to be extended for real application
void run() {
// We run the main menu in a loop as long as the machine is active
bool active{ true };
while (active) {
// Show main menu
std::cout << "\n\n\nMain menu. Please select:\n 1 --> Get Item\n 0 --> Exit\n\nOption: ";
// Get user selection
unsigned int option; std::cin >> option;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// Depending on the user selected action
switch (option) {
case 0:
// Leave function.
active = false;
std::cout << "\n\nExiting . . .\n";
break;
case 1:
// Get an item
std::cout << "\n";
getItem();
break;
default:
std::cout << "\n\n\nError: Wrong selection. Please try again\n";
break;
}
}
}
};
int main() {
// Define a Vending Machine. Read data from disk
VendingMachine vendingMachine;
// SHow what is in initially
vendingMachine.displayContent();
// Run the machine
vendingMachine.run();
// Show, what is now in the machine
vendingMachine.displayContent();
// Destructor of vendingMachine will be called and file automatically saved
return 0;
}

Related

How do I check whether an index of array is empty and then, if it is, skip to the next?

I'm trying to build a program that can register a user to the database (still learning cpp, I hope that in the near future I'll be able to work with database).
What I'm trying to do with this code is to check whether an index of array is empty for the user to store an ID in it. If it isn't empty, I want the program to keep looking for an empty index of array, for the new info to be stored in.
Here is the code:
void registro() {
std::string userid[3];
userid[0] = "Houkros"; // eventually I'll try to have this being read from a file or server database..
std::string userpass[3];
std::string usermail[3];
std::string userkey[3];
std::string getUid[3];
std::string getUpass[3];
std::string getUmail[3];
std::string getUkey[3];
std::cout << std::endl << " >>>> REGISTRATION <<<< " << std::endl;
std::cout << " =============================================== " << std::endl;
std::cout << std::endl;
std::cout << "Please, enter the desired user id: " << std::flush;
if (userid[0].empty())
{
std::cin >> userid[0];
}
else {
std::cin >> userid[1];
}
for (int i = 0; i < 2; i++)
{
std::cout << " Element of array: " << i << " is > " << userid[i] << std::endl;
}
Please consider the following definitions for an "empty" array element:
a) not initialised (unhelpful, cannot be checked)
b) never yet written to (same as a) )
c) contains "" (possible, but means that "" must not be accepted as an actual content)
d) is empty according to a second array in which that info is maintained (this is what I almost recommend)
e) contains a struct with a string and a maintained "empty" flag (this I recommend)
Whatever you do, make sure that you init all variables and array elements before first read-accessing them; i.e. in all cases first write something meaningful to it.

Deferring cout output until just prior to the next output

I have some C++ console programs that display progress information on the last line of output, at regular intervals.
This progress line is cleared prior to writing the next real output (or updated progress information); this could be from a number of different places in the source, and I'm currently clearing the progress line on each one, e.g.:
cout << clearline << "Some real output" << endl;
...
cout << clearline << "Some other real output" << endl;
...
cout << clearline << setw(4) << ++icount << ") " << ... << endl;
...
cout << clearline << "Progress info number " << ++iprog << flush;
Here, 'clearline' is some (system dependent) string like "\r\33[2K" which clears the current last line.
I would prefer something cleaner, that localises source changes to the actual line that's going to be cleared, like simply:
cout << "Progress info number " << ++iprog << flush << defer_clearline;
where 'defer_clearline' causes the writing of 'clearline' to be deferred until just prior to the next cout output, wherever and whatever that happens to be. I then wouldn't need to use 'clearline' on all the other lines.
I thought it might be possible to do this if 'defer_clearline' is a manipulator, and/or using xalloc() and iword().
But I've not managed to get anything that works.
Is it possible to do this sort of thing, and if so how?
2020-12-30: edited to include missing 'flush's.
You can pretty easily setup an std::cout wrapper:
// Declare the empty struct clear_line and instantiate the object cls
struct clear_line { } cls;
class out {
private:
std::ostream &strm = std::cout;
bool is_next_clear = false;
public:
template <typename T>
out& operator<<(const T& obj) {
if(is_next_clear) {
strm << std::endl << std::endl << std::endl; // clear logic
is_next_clear = false;
}
strm << obj;
return *this;
}
out& operator<<(const clear_line& _) {
is_next_clear = true;
return *this;
}
};
This pretty simply stores an is_next_clear bool for whether or not the next regular output should be cleared. Then, in the general case (the templated operator<<()), we run your clear logic and flip the is_next_clear flag if applicable. Then just output as usual.
Then, the operator<<() is overloaded for the case of a clear_line object. So if one of those is sent, we know to flip the is_next_clear flag, but not actually output anything.
Here's an example use:
int main() {
out o;
o << "Some real output" << cls;
o << "Some other real output";
return 0;
}
Here it is in action: https://ideone.com/0Dzwlv
If you want to use endl, you'll need to add a special overload for it as this answer suggests: https://stackoverflow.com/a/1134467/2602718
// this is the type of std::cout
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
// this is the function signature of std::endl
typedef CoutType& (*StandardEndLine)(CoutType&);
// define an operator<< to take in std::endl
out& operator<<(StandardEndLine manip)
{
// call the function, but we cannot return its value
manip(strm);
return *this;
}
Live example: https://ideone.com/ACUMOo

Final element of list corrupted after removing an element

I'm having a weird issue. I'm writing a function to delete a line from a list of names created elsewhere, which, after some research, seems like it should be fairly simple. I write the current list of names into a list, display the list, have the user input the name they want to delete, remove the user-inputted name from the list, then display the updated list to the user.
Up to here, everything works perfectly, but when I write the list back into the file, the last name gets a random amount of characters chopped off of it, ranging from a couple of characters to the entire line. Now, this is where it gets strange. If I open the file and look at it without exiting the program, the last line of the file is messed up and continues to be whenever I display it later in the program. But, if I exit the program and then open the file, the last line is back to how it was originally written! That file is not written to again by the program after the list is written in, so I cannot imagine why this is happening.
I almost decided that since the file ultimately comes out of the program correct, I could just ignore the issue, but I want the user to be able to view the list of names after the deletion for various reasons, which is made impossible while the last list item prints incorrectly.
I am still fairly beginner with C++, so I'm kind of hoping that this is just an issue of me not fully understanding lists or something. Regardless, dumbed down explanations would be ace.
I included the function below, any help is much appreciated.
char act, charname[50];
string namestr;
list <string> c1;
list <string>::iterator c1_Iter;
//write the names from the file into a list
ifstream names("List of Names.txt");
while (std::getline(names, namestr))
{
c1.push_back(namestr);
}
//print the current names
cout << "Registered names:";
for (c1_Iter = c1.begin(); c1_Iter != c1.end(); c1_Iter++)
cout << "\n" << setw(5) << " " << *c1_Iter;
//choose which names to delete and confirm
cout << "\n\nEnter the name you would like to delete: ";
cin.getline(charname, 50);
cin.getline(charname, 50);
cout << "\nAre you sure? Enter 'y' to permanently delete " << charname << ", and any other key to return to the start screen.";
cin >> act;
if (act == 'y' || act == 'Y')
{
//delete a file associated with each name
string strname(charname);
strname.append(".txt");
if (remove(strname.c_str()) < 0)
perror("Error deleting file");
else
{
//delete name from the file only if that person's individual file is successfully deleted
c1.remove(charname);
cout << "\n" << charname << " successfully deleted!\n";
//print the updated list of names
cout << "\nUpdated list of registered names:\n";
for (c1_Iter = c1.begin(); c1_Iter != c1.end(); c1_Iter++)
cout << *c1_Iter << endl;
//write updated list of names over "List of Names" to update the file
ofstream newNames("List of Names.txt");
for (c1_Iter = c1.begin(); c1_Iter != c1.end(); c1_Iter++)
newNames << *c1_Iter << endl;
newNames.close();
}
}
As Mohit Jain mentioned in the comments, you need to call names.close() on the ifstream before opening the file for writing as a separate ofstream. Also, you can use a std::string charname rather than char charname[50].
You could also use an fstream with appropriate seeking. If I'm not mistaken have active ifstream and ofstream objects handling the same file can lead to undefined behavior.
Here's a more C++ friendly solution:
#include <iostream>
#include <string>
#include <fstream>
#include <list>
#include <iomanip>
int main()
{
char act;
std::string charname;
std::string namestr;
std::list<std::string> c1;
std::list<std::string>::iterator c1_Iter;
//write the names from the file into a list
std::ifstream names("names.txt");
while (std::getline(names, namestr))
{
c1.push_back(namestr);
}
//print the current names
std::cout << "Registered names:";
for (c1_Iter = c1.begin(); c1_Iter != c1.end(); c1_Iter++)
std::cout << "\n" << std::setw(5) << " " << *c1_Iter;
//choose which names to delete and confirm
std::cout << "\n\nEnter the name you would like to delete: ";
std::cin >> charname;
std::cout << "\nAre you sure? Enter 'y' to permanently delete " << charname << ", and any other key to return to the start screen.";
std::cin >> act;
if (act == 'y' || act == 'Y')
{
//delete a file associated with each name
std::string strname(charname);
strname.append(".txt");
if (remove(strname.c_str()) < 0)
{
std::cerr << "Error deleting file " << strname << std::endl;
return 1;
}
else
{
//delete name from the file only if that person's individual file is successfully deleted
c1.remove(charname);
std::cout << "\n" << charname << " successfully deleted!\n";
//print the updated list of names
std::cout << "\nUpdated list of registered names:\n";
for (c1_Iter = c1.begin(); c1_Iter != c1.end(); c1_Iter++)
std::cout << *c1_Iter << std::endl;
//write updated list of names over "List of Names" to update the file
names.close(); //Close the ifstream before opening the file for editing
std::ofstream newNames("names.txt");
for (c1_Iter = c1.begin(); c1_Iter != c1.end(); c1_Iter++)
newNames << *c1_Iter << std::endl;
newNames.close();
}
}
return 0;
}

C++ Read from file to vector

I'm trying to convert a string streamed phone lookup program into file streamed.. I'm missing something, but I'm stuck.. what members can I use in the ofstream process to get this working?
ofstream& process (ofstream &os, vector<PersonInfo> people)
{
// for each entry in people
for (vector<PersonInfo>::const_iterator entry = people.begin();
entry != people.end(); ++entry) {
ofstream formatted, badNums; // objects created on each loop
// for each number
for (vector<string>::const_iterator nums = entry->phones.begin();
nums != entry->phones.end(); ++nums) {
if (!valid(*nums)) {
badNums << " " << *nums; // string in badNums
} else
// ``writes'' to formatted's string
formatted << " " << format(*nums);
}
if (badNums.empty()) // there were no bad numbers
os << entry->name << " " // print the name
<< formatted.str() << endl; // and reformatted numbers
else // otherwise, print the name and bad numbers
cerr << "input error: " << entry->name
<< " invalid number(s) " << badNums.str() << endl;
}
return os;
}
First, you don't want an ofstream, except at the point you're opening
the file (creating the instance). The output stream interface is
defined by std::ostream; std::ofstream derives from this, as does
std::ostringstream (output can become an std::string), and in most
applications, a couple of others written by the local programmers. In
your case (if I've understood the problem correctly), what you want is:
std::ostream& process( std::ostream& os,
std::vector<PersonInfo> const& people )
// Note the use of a const reference above. No point
// in copying the entire vector if you're not going to
// modify it.
{
for ( std::vector<PersonInfo>::const_iterator entry = people.begin();
entry != people.end();
++ entry ) {
std::ostringstream formatted;
std::ostringstream badNums;
// ...
if ( badNums.str().empty() ) {
os << ... << formatted.str() << std::endl;
} else {
os << ... << badNums.str() << std::endl;
}
}
return os;
}
Note the different types: std::ostream formats output, independently
of the destination type. std::ofstream derives from it, and provides
a file as destination. std::ostringstream derives from it, and
provides a std::string as destination type. And the std::ostream
takes a std::streambuf* as argument, and you provide the destination
type.
You never associate a file with ostream, so the compiler doesn't know what to do with the data you write into it.
ofstream& process (ofstream &os, vector<PersonInfo> people)
{
os.open("Data.txt"); //open file to be used
if(!os.is_open())
std::cerr << "Error opening file!\n";
//rest of code goes here
}
EDIT: after reading through your program again, i noticed you're using ofstream wrong. Ofstream is for opening and writing FILES.The program has a lot of syntax and logical errors i would read up on it more here.
It looks like you don't need to use ofstreams for the internal parts of this function. In fact you don't need to use streams at all, a std::string would do:
ofstream& process (ofstream &os, vector<PersonInfo> people)
{
// for each entry in people
for (vector<PersonInfo>::const_iterator entry = people.begin();
entry != people.end(); ++entry) {
string formatted, badNums; // objects created on each loop
// for each number
for (vector<string>::const_iterator nums = entry->phones.begin();
nums != entry->phones.end(); ++nums) {
if (!valid(*nums)) {
badNums += " " + *nums; // string in badNums
} else
// ``writes'' to formatted's string
formatted += " " + format(*nums);
}
if (badNums.empty()) // there were no bad numbers
os << entry->name << " " // print the name
<< formatted << endl; // and reformatted numbers
else // otherwise, print the name and bad numbers
cerr << "input error: " << entry->name
<< " invalid number(s) " << badNums << endl;
}
return os;
}

How to input a file into C++ and comparing console input

Im working on my homework assignment and I stuck because in the assignment we have to ask the user to enter a file name but also to type in either wc cc or lc (word count, character count, and line count of a file. For example, wc filename.txt. Im suppose to check the file to see if its valid or not which i understand and I know how to compare the users input to determine the different kind of function to run, but I dont understand how you could do it together. Any ideas? This is what I have so far.
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
string line;
string file;
ifstream input; //input file stream
int i;
cout << "Enter a file name" << endl;
while(true){
cout << ">" ;
getline(cin,file);
input.open(file.c_str());
if (input.fail()) {
cerr << "ERROR: Failed to open file " << file << endl;
input.clear();
}
else {
i = 0;
while (getline(input, line))
if(line == "wc"){
cout << "The word count is: " << endl;
}
else if(line == "cc"){
cout << "The character count is: " << endl;
}
else if(line == "lc"){
cout << "The line count is: " << endl;
}
else if(line == "exit"){
return 0;
}
else{
cout << "----NOTE----" << endl;
cout << "Available Commands: " << endl;
cout <<"lc \"filename\"" << endl;
cout <<"cc \"filename\"" << endl;
cout <<"wc \"filename\"" << endl;
cout <<"exit" << endl;
}
}
}
return 0;
}
void wordCount(){
//TBD
}
void characterCount(){
//TBD
}
void lineCount(){
//TBD
}
You have to find the space between the command and the file name in the users input and then split the string where you find the space. Something like this
cout << "Enter a command\n";
string line;
getline(cin, line);
// get the position of the space as an index
size_t space_pos = line.find(' ');
if (space_pos == string::npos)
{
// user didn't enter a space, so error message and exit
cout << "illegal command\n";
exit(1);
}
// split the string at the first space
string cmd = line.substr(0, space_pos);
string file_name = line.substr(space_pos + 1);
This is untested code.
You could do better than this, for instance this would not work if the user entered two spaces between the command and the file name. But this kind of work rapidly gets very tedious. As this is an assignment I would be tempted to move on to more interesting things. You can always come back and improve things later if you have the time.
I think you are asking how to validate multiple arguments: the command and the file.
A simple strategy is to have function like the following:
#include <fstream> // Note: this is for ifstream below
bool argumentsInvalid(const string& command, const string & command) {
// Validate the command
// Note: Not ideal, just being short for demo
if("wc" != command && "cc" != command && "lc" != command) {
std::cout << "Invalid command" << std::endl;
return false;
}
// Validate the file
// Note: This is a cheat that uses the fact that if its valid, its open.
std::ifstream fileToRead(filename);
if(!fileToRead) {
std::cout << "Invalid file: \"" << filename << "\"" << std::endl;
return false;
}
return true;
// Note: This does rely on the ifstream destructor closing the file and would mean
// opening the file twice. Simple to show here, but not ideal real code.
}
If you want to evaluate ALL arguments before returning an error, insert a flag at the top of that function, like:
// To be set true if there is an error
bool errorFound = false;
and change all of the returns in the conditions to:
errorFound = true;
and the final return to:
return !errorFound;
Usage:
....
if(argumentsInvalid(command, filename)) {
std::cout << "Could not perform command. Skipping..." << std::endl;
// exit or continue or whatever
}
// Now do your work
Note: The specific validity tests here are over simplified.