For loop iterator issues c++ - c++

I having a problem getting my code to throw runtime errors as well getting these loops to work properly, and I'm not sure why.
If the user were to enter a name and balance, for example: josh 100, the program should add 100 to the account named josh in listAccounts vector, and then print the new balance.
something about the line: for ( ; iter != listAccounts.end(); iter++) {
For some reason it's causing the if statements to not work, if I comment out that line the function works but only with the last element in listAccounts. Is something wrong with the for loop that is causing it to not step through each element?
void Account::change_balance(string name, int balance, int i) {
try {
auto iter = listAccounts.begin();
for ( ; iter != listAccounts.end(); iter++) {
if ((*iter).account_name == name) {
if (((*iter).account_type == 0) && (((*iter).account_balance +
balance) < 0)) {
throw runtime_error("account cannot hold negative
balance");
(*iter).account_balance = (((*iter).account_balance) -
(balance));
cout << (*iter).account_balance;
} else {
(*iter).account_balance = balance +
(*iter).account_balance;
cout << (*iter).account_balance;
}
}
}
}
catch (runtime_error& e) {
cout << "error on line " << i << ": " << e.what() << "\n";
}
}
I can't figure out where I have gone wrong, any indication would be much appreciated. Thanks.

Check your inner for-loop. You never increase the iterator, all you do is comparing listAccounts.size() times the same element of the vector with the function argument. As the loops ends when k == listAccounts.size(), the condition n == listAccounts.size() can only be fulfilled when the outer loop goes for at least another run, because you did not clear n (or defined it locally). But now, it may fire and say "Account not found" even though it is there, because n == listAccounts.size(). This is not the behaviour your intended.
You should rethink your algorithm. Why do you traverse all the accounts each time you traverse the list of accounts? It would be totally sufficient to do it once. Consider doing something like this:
for(auto& account : listAccounts) {
if(account.account_name == name) {
// do your logic stuff and eventually:
return;
}
}
// if your program gets here, no account with such a name was found

Related

Sorting and using a binary search on a vector of object pointers

I'm trying to write some c++ code that will perform a binary search on a vector of object pointers. For reference my program is like a bank account system where a user can open a bank account and make deposits/withdrawals on said bank account, which in turn create a new transaction object that is pushed back to a list of Transaction pointer objects like so:
in main.cpp:
else if (userCommand == "withdraw")
{
try
{
int index;
float amount;
time_t currentTime = time(nullptr); //gets current time
cout << "Please pick the account you wish to withdraw from starting from 1: " << endl;
cin >> index;
if (index > allAccounts.size() || index <= 0) throw InvalidIndexException(); //0 is not an option for any other command other than withdraw so we include it for the exception
cout << "Please give the amount of money you wish to withdraw: " << endl;
cin >> amount;
if (amount < 0) throw NegativeValue("Withdrawal");
allAccounts[index - 1]->withdraw(amount);
Transaction* accountTransaction = new Transaction("Withdrawal", currentTime, amount); //creates new Transaction object that will be passed into the account object
allAccounts[index - 1]->addTransaction(accountTransaction);
}
catch (ExceedOverDraftException)
{
cout << "Current account overdraft exceeded" << endl;
}
catch (NegativeWithdrawalException)
{
cout << "Savings account cannot go below 0" << endl;
}
catch (InvalidIndexException)
{
cout << "Index given does not exist" << endl;
}
catch (NegativeValue e)
{
cout << e.getMessage() << endl;
}
}
else if (userCommand == "deposit")
{
try
{
int index;
float amount;
time_t currentTime = time(nullptr);
cout << "Please pick the account you wish deposit to starting from 1: " << endl;
cin >> index;
if (index > allAccounts.size() || index <= 0) throw InvalidIndexException();
cout << "Please give the amount of money you wish to deposit: " << endl;
cin >> amount;
if (amount < 0) throw NegativeValue("Deposit");
allAccounts[index - 1]->deposit(amount);
Transaction* accountTransaction = new Transaction("Deposit", currentTime, amount);
allAccounts[index - 1]->addTransaction(accountTransaction);
}
catch (InvalidIndexException)
{
cout << "Index does not exist" << endl;
}
catch (NegativeValue e)
{
cout << e.getMessage() << endl;
}
}
else if (userCommand == "search")
{
try
{
int index;
float valueAnswer;
int transactionsSize;
cout << "Please say which account you wish to search transaction for starting from 1" << endl;
cin >> index;
if (index > allAccounts.size()) throw InvalidIndexException();
transactionsSize = allAccounts[index - 1]->getHistorySize();
cout << "Please state what value you wish to search for" << endl;
cin >> valueAnswer;
cout << allAccounts[index - 1]->search(valueAnswer, 0, transactionsSize - 1);
}
catch (InvalidIndexException)
{
cout << "Index given does not exist";
}
catch (TransactionNotFound e)
{
cout << e.getMessage();
}
}
in account.cpp:
void Account::addTransaction(Transaction* t)
{
history.push_back(t); //as the vector is full of pointers the parameter must also be a pointer.
sort(history.begin(), history.end());
}
Transaction Account::search(float value, int start, int end) throw (TransactionNotFound) //search function is a binary search
{
if (start <= end)
{
for (int i = 0; i < history.size(); i++)
{
cout << history[i]->getValue() << endl;
}
int mid = (start + end) / 2; //midpoint of the vector
if (history[mid]->getValue() == value) return *history[mid];
else if (history[mid]->getValue() < value) return search(value, mid + 1, end);
else if (history[mid]->getValue() > value) return search(value, start, mid - 1);
}
else throw TransactionNotFound(value); //if the array has been searched to the point that start has reached the end the transaction doesn't exist
}
As you can see the main idea is that every time a new transaction is made, either from calling deposit or withdraw, an object of transaction is made which is then added to the vector and then should be sorted in ascending order. To sort I tried to follow a guide from an older stack overflow message that was using struct objects rather than class objects (Sorting a vector of custom objects) which seemed to use operator overloading of the < operator on the struct object that would then allow the vector of struct objects to be sorted, which I tried adapting in my transaction class as a friend function like so:
in transaction.cpp:
bool operator <(const Transaction& t1, const Transaction& t2)
{
return (t1.value < t2.value); //checks if the next value in the vector is greater than the current value in the vector.
}
However, I have tested the sort method and it doesn't seem to be sorted the objects as I expect it to, in that it doesn't seem to be sorted the objects at all, causing my TransactionNotFound exception to be thrown. I'm genuinely not sure what I've done wrong, I'm assuming it is how I've done my operator overload, but I don't know what I need to do to fix it.
Sorry if my code looks awful, I think I realised that my try and catch blocks could go outside the if statement. Also if there is any information about my code that might be needed please let me know, I'm new to stack overflow in general so I'm not sure if what I've included is needed or not.
Any help people can give is really appreciated!
Besides many many design flaws, you have several major semantic errors.
1st, and most important, and the reason, why sort does not work:
You store pointers in your std::vector "history". But in the compare function, you compare references, so values and NOT pointers.
So, the sort function will not sort according to the values stored in the transaction, but according to their address in memory, which you do not know and may be random (but out of your control),
If you create one Transaction after the other, then they may be created in with ascending memory addresses. And then std::sort will do nothing. But that is pure coincidence.
So, you need to change your custom sort function. For the simplicity of it, I will use a Lambda. Then the sortstatement would look like this:
std::sort(history.begin(), history.end(),
[](const Transaction* t1, const Transaction* t2) {return t1->value < t2->value; });
Then the sort function will do what you want.
2nd, The search function is implemented wrongly.
The stop condition for the recursion is incorrect. It must be if (end < start).
Also the calculation of mid is wrong. Use a piece of paper and draw a picture, then you will see it. The correct statement is:
int mid = start + (end-start) / 2;
You always need to add the new start value. Otherwise you can never reach the upper half of the interval.
Then, a very important statement. You cannot rely on an equal comparison for float types. Therefore, if you want to calculate with currencies, then do not use float or double values. Use integer types * 100.
Now let use look at a minimum reproducible example:
#include <iostream>
#include <string>
#include <ctime>
#include <vector>
#include <algorithm>
struct Transaction {
std::string type{};
time_t time{};
int value{};
int getValue() { return value; }
};
struct Account {
std::vector<Transaction*> history{};
void addTransaction(Transaction* t) {
history.push_back(t);
std::sort(history.begin(), history.end(),
[](const Transaction* t1, const Transaction* t2) {return t1->value < t2->value; });
}
Transaction* search(float value) {
return bsearch(value, 0, history.size()-1);
}
Transaction* bsearch(float value, int start, int end)
{
for (unsigned int i = 0; i < history.size(); i++) {
// std::cout << history[i]->getValue() << '\n';
}
if (end < start) return nullptr;
int mid = start + (end-start) / 2; //
if (history[mid]->getValue() == value)
return history[mid];
else if (history[mid]->getValue() < value) return bsearch(value, mid + 1, end);
else return bsearch(value, start, mid - 1);
}
};
int main() {
Account account{};
// Add some demo transactions
account.addTransaction(new Transaction{ {},{},9 });
account.addTransaction(new Transaction{ {},{},8 });
account.addTransaction(new Transaction{ {},{},7 });
account.addTransaction(new Transaction{ {},{},6 });
account.addTransaction(new Transaction{ {},{},5 });
account.addTransaction(new Transaction{ {},{},4 });
account.addTransaction(new Transaction{ {},{},3 });
account.addTransaction(new Transaction{ {},{},2 });
account.addTransaction(new Transaction{ {},{},1 });
Transaction* searchResult{};
searchResult = account.search(4);
if (searchResult)
std::cout << "\nFound: " << searchResult->getValue() << '\n';
else
std::cout << "\nError: 4 not found\n\n";
searchResult = account.search(42);
if (searchResult)
std::cout << "\nFound: " << searchResult->getValue() << '\n';
else
std::cout << "\nError: 42 not found\n\n";
return 0;
}
So, maybe you get the idea.
But now, and more important. The design flaws.
You are using tons of exceptions. And you use them like if statements.
DO NOT do this. Use exceptions for exceptionally problems, not for minor stuff that can be handled by simple if statements.
You should make use of C++ and object oriented programming.
A "deposit" and a "withdrawal" ar both Transactions. So create a base class "Transaction" and derive a class "Deposit" and a class "Withdrawal" from that.
Do not use raw pointers for owned memory, but std::unique_ptr. Otherwise, you create tons of memory leaks. If you write new, then, somehwere, you need to write delete as well.
Or use smart pointers, like the unique_ptr. That will do the job for you.
In general, a good hint is, to always make a design, for example on a piece of paper or however, and then start programming.
Anyway, please continue your good work.

How to remove only one element from a vector with duplicates

I am making a D&D game in C++. I roll 6 scores randomly, put them in a vector, display them to the player. Then I go through each ability (str, dex, con, int, wis, and cha), and call a function that asks the player which of the scores they want to use for each ability, and then i remove it from the vector, return the value, and move on to the next ability. It works fine unless there is a duplicate roll, in which case it deletes both of the duplicates. I want it to only remove one at a time regardless of duplicates, and I haven't been able to find anything online to do this. Here is the function call
int Character::initScores(std::vector<int> & v, std::string ability)
{
int c = 0;
bool error = 0;
do {
if (c != 0) {
std::cout << "That isn't one of your scores. Try again. " <<
std::endl;
}
int choice;
std::cout << ability << ": ";
std::cin >> choice;
if (std::find(v.begin(), v.end(), choice) != v.end())
{
v.erase(std::remove(v.begin(), v.end(), choice), v.end());
std::cout << "Your remaining rolls are ";
for (int i = 0; i < v.size(); i++)
std::cout << v[i] << " ";
std::cout << std::endl;
return choice;
}
else
{
c++;
error = 1;
}
} while (error = 1);
}
And the function calls
std::cout << "Enter which score you want for... " << std::endl;
strength = initScores(scores, "Strength");
dexterity = initScores(scores, "Dexterity");
constitution = initScores(scores, "Constitution");
intelligence = initScores(scores, "Intelligence");
wisdom = initScores(scores, "Wisdom");
charisma = initScores(scores, "Charisma");
Also please lmk if there is anything inefficient/bad practice in my code, I have only recently started working on my own coding projects
You are calling std::remove(), which "removes" ALL matching values from the container (really, it just moves them to the end of the container), and then you are calling the 2-parameter overload of the erase() method to physically delete ALL of the "removed" values from the container.
If you just want to remove 1 element, pass the iterator returned by std::find() to the 1-parameter overload of the erase() method:
auto iter = std::find(v.begin(), v.end(), choice);
if (iter != v.end())
{
v.erase(iter);
...
}

How to determine if a line from a input file is the last line? c++

I have written a program to check for balanced curly brackets in a .cpp file. The program works fine and finds the syntax error, displays the number of the line with the problem and then exits.
But I have to display a different error message if the error is at the last line of the input cpp file.
I have tried to implement it like following way but I think it is wrong. It doesn't work anyway :)
else
{
if(current == inputFile.eof()) //THIS IS WHAT I TRIED
{
cout << "Syntax error at the end of the program.";
}
else
{
cout << "Syntax error in line: " << current << "\n";
errorFound == true;
}
}
I did not give the complete code because I think a simple if condition with the correct variable will solve this. If you need it, I can post the code later.
EDIT: Larger piece of the code is given as requested. counter is an int variable that is updated every line by counter++.
for(int i = 0; i < line.length(); i++)
{
if (line[i] == '{')
{
stack.push(current);
}
else if(line[i] == '}')
{
if (!stack.isEmpty())
{
stack.pop(opening);
cout << "Code block: " << opening << " - " << current << "\n";
}
else
{
if(current == inputFile.eof())
{
cout << "Syntax error at the end of the program.";
}
else
{
cout << "Syntax error in line: " << current << "\n";
errorFound == true;
}
}
}
This is the best solution I could think of. There is probably a better one.
std::ifstream input_file{ "file.txt };
std::vector<std::string> contents;
// fill vector with file contents
std::string cline;
while (std::getline(input_file, cline))
contents.push_back(cline);
// now loop
for (const auto& line : contents) {
//...
if (&line == &contents.back()) {
// do something at the end of file
}
}
You can use an iterator version if you don't like the pointer comparison :)

How are these continue statements affecting my code?

I have been working on this little program in Visual Basic 2013 in an attempt to create a sort of tiered structure for user-input commands. Basically, I want the first of a two word input to direct the program to an area of code with a set of responses for the second word. In this program, the first word can either be "human" or "animal." These words direct the program to functions that select the kind of animal or human.
#include "stdafx.h"
#include <iostream>
#include <sstream>
void ifAnimal(std::string b) //This is the set of responses for a first word of "Animal"
{
if (b == "pig")
{
std::cout << "It is a pig." << std::endl;
}
if (b == "cow")
{
std::cout << "It is a cow." << std::endl;
}
}
void ifHuman(std::string b) //This is the set of responses for a first word of "Human"
{
if (b == "boy")
{
std::cout << "You are a boy." << std::endl;
}
if (b == "girl")
{
std::cout << "You are a girl." << std::endl;
}
}
int main()
{
while (1)
{
std::string t;
std::string word;
std::cin >> t;
std::istringstream iss(t); // Set up the stream for processing
int order = 0;
//use while loop to move through individual words
while (iss >> word)
{
if (word == "animal")
{
order = 1;
continue; //something wrong with these continues
}
if (word == "human")
{
order = 2;
continue;
}
if (order == 1)
{
std::cout << "The if statement works" << std::endl;
ifAnimal(word);
}
if (order == 2)
{
std::cout << "This one too" << std::endl;
ifHuman(word);
}
}
}
return 0;
}
The problem is that whenever the program reaches the continue statements, the if statements calling my functions are not triggered. No text is displayed at all. If the continue statements are removed, the if statements trigger, but then the corresponding function has the wrong word. Am I unaware of something that those continues are doing? Is there a better way to accomplish what I want to do?
what continue means is skip the rest of the loop and go back to the top. whatever is after the continue statement won't be executed if continue gets hit.
It looks like you expect your word to be two words at the same time, so once you execute ifAnimal() none of the cases in ifAnimal will be met. word will never be "pig" or "cow" when you call that method because you only ever call that method when word is equal to "animal", and you don't change it after that.
Continue means "Go immediately to the top of the loop, and start over again". You do not want that at all.
//use while loop to move through individual words
while (iss >> word)
{
if (word == "animal")
{
order = 1;
}
else if (word == "human")
{
order = 2;
}
if (order == 1)
{
std::cout << "The if statement works" << std::endl;
ifAnimal(word);
}
if (order == 2)
{
std::cout << "This one too" << std::endl;
ifHuman(word);
}
}

C++ exception handling, error

I am using a library, RapidXML, but my problem is more general. The library parses xml like item->first_node("CRAP")->first_node("CRAP") Now, if I put this in an if statement it will crash. If I put this: item->first_node("CRAP") it won't.
I am a beginner in C++ and I don't know much about exceptions but:
try
{
if(item->first_node("CRAP")->first_node("CRAP"))
{
}
cout << "OK";
} catch (...)
{
cout << "CRASH";
}
The above crashes. How to check if my node exists without crashes (and without looping all the items one by one)?
You simply need to take it one step at a time:
if (item != 0) // check if item is null
{
rapidxml::xml_node<char>* node = item->first_node("CRAP"); // Try to grab first child node
if (node != 0)
{
// okay got a valid node, grab next one
rapidxml::xml_node<char>* next = node->first_node("CRAP");
if (next != 0)
{
// Okay
}
}
}
When you try it in one step, i.e. item->first_node("CRAP")->first_node("CRAP"), you never check that the first call to first_node returned a null pointer (assuming item is a valid pointer also).
Sounds like either item is NULL or item->first_node("CRAP") is returning NULL. Try this, see what output you get:
try
{
node *n; // <-- use whatever type first_node() actually returns
if (!item)
cout << "item is NULL";
else
{
n = item->first_node("CRAP");
if (!n)
cout << "first node is NULL";
else
{
n = n->first_node("CRAP");
if (!n)
cout << "second node is NULL";
else
cout << "OK";
}
}
}
catch (...)
{
cout << "CRASH";
}
Always test whether an expression is NULL before using it as part of a longer expression. Never write things like
if(item->first_node("CRAP")->first_node("CRAP"))
if first_node("CRAP") can return NULL. Instead, write something like
if(item->first_node("CRAP") && item->first_node("CRAP")->first_node("CRAP"))
This works because the '&&' (logical and) operator uses lazy evaluation: it won't bother to evaluate its second operand if the first one evaluates to false.