I am reading a Data Structures and Algorithms book and there is a case study on recursion, specifically a parser that uses recursive descent. I'm a bit new to C++ (I'm learning it on the side and as I go with Stanley B. Lippman's C++ Primer 5th edition).
I'm a bit stuck on code and am trying to make sense of it. Is what I've written at the bottom (bulleted points) accurate descriptions of what is happening in the functions? I would post the header, but it's a bit too long, you can probably find it online if you search "Data Structures
and Algorithms in C++ by Adam Drozdek - interpreter.h".
double Statement::findValue(char* id) {
IdNode tmp(id);
list<IdNode>::iterator i = find(idList.begin(), idList.end(), tmp);
if (i != idList.end())
return i->value;
else
issueError("Unknown variable");
return 0;
}
void Statement::processNode(char* id, double e) {
IdNode tmp(id, e);
list<IdNode>::iterator i = find(idList.begin(), idList.end(), tmp);
if (i != idList.end())
i->value = e;
else
idList.push_front(tmp);
}
findValue()
Looks for a value for a certain variable
Uses an iterator i so that it can traverse the list
Looks for tmp using find()
If i doesn't equal the value at the end of the list, return it
Other wise, the variable cannot be found
processNode()
Processes nodes by using an iterator i
Looks for a variable that matches tmp
Finds the variable and sets it's value to the value of e
Other wise, store variable on to idList to be evaluated later
Define a function that accepts a pointer to char (likely it's an array) and returns a double:
double Statement::findValue(char* id) {
Create a temporary (will die at 'return') of type IdNode based on that pointer:
IdNode tmp(id);
Use a (std:: I guess, but it could be any function with the same features) function that looks for tmp inside the container idList. The result is an interator i, which must be of the same type of that one used by the container:
list<IdNode>::iterator i = find(idList.begin(), idList.end(), tmp);
Check if something is found. idList.end() means "one past end", beyond the last item in the container:
if (i != idList.end())
Return the member value (which is part of IdNode) for the item found. If value is not a double then convert to it.
return i->value;
Otherwise, call issueError function.
else
issueError("Unknown variable");
Exit function, returning a double with value = 0:
return 0;
}
Same, but: this function accepts two parameters and returns nothing:
void Statement::processNode(char* id, double e) {
Same, but: IdNode constructor uses two parameters:
IdNode tmp(id, e);
Same
list<IdNode>::iterator i = find(idList.begin(), idList.end(), tmp);
Same
if (i != idList.end())
Now, modify the item found. Just update value member:
i->value = e;
Otherwise, add tmp, insert it at the very begining of idList container.
else
idList.push_front(tmp);
Exit function:
}
Your understanding is mostly right. I'll describe it statement by statement
in somewhat more precise terms.
double Statement::findValue(char* id) {
IdNode tmp(id);
This constructs a variable named tmp using the string passed in. (Well, I presume id is a string. In C, it's common to pass a pointer to the beginning of a zero-terminated array of characters when you want to pass a string. In C++, the same thing was common, but it's becoming less common now that we have better string and range types in the standard library. But not every char * is a string. It might just be a pointer to a single character. Here though, the context strong suggests it's a string.)
list<IdNode>::iterator i = find(idList.begin(), idList.end(), tmp);
Uses the std::find algorithm to search the range of elements from the beginning of the idList to the end for an element that equals tmp.
Note the idList.end() is an iterator that indicates the actual end of the list rather than the last item in the list. You can think of it as one element beyond the last element in the list. Iterator ranges (and array indices) in C and C++ are typically inclusive of the first value and exclusive of the second. So find will start with the first element and continue up to (but not through) the end of the list.
It's not shown here, but I assume there's an overload for operator==(const idNode &, const idNode &) that returns true if the names of the two idNodes match, regardless of whether the value fields match.
If there's no match in the range, then std::find is going to return the end iterator. If there is a match, it's going to return an iterator that references the matching element. So ...
if (i != idList.end())
return i->value;
else
issueError("Unknown variable");
This returns the value field of the match (if there is one) or it calls issueError if there isn't.
return 0;
}
In the latter case, assuming issueError returns and doesn't terminate the program or throw an exception, the function will return a value of 0 (which, because the return type is a double, will be implicitly converted to 0.0).
processNode is nearly identical except that it sets the value in the found node rather than returning it.
Related
I am trying to do dfs over a graph defined as unordered_map<string,set<pair<string,int>>> g;
So here is my dfs code:
void dfs(string u){
for(auto v=g[u].begin();v!=g[u].end();v++){
if(!v->second){
cout<<v->first<<endl;
res.push_back(v->first);
v->second = 1;
dfs(v->first);
}
}
}
I am trying to change the value of v->second to 1, but I am getting an error of
cannot assign to return value because function 'operator->' returns a const value
v->second = 1;
So is there any other way to change the second value of the pair?
You cannot modify value of an element of std::set as it would break it's invariant. What you can do though is to remove existing element and insert modified one. But as you iterate over the set then such modification inside loop would be overcomplicated. So I suggest you remove all elements that satisfy your condition and then insert them all back modified.
Though it is not clear why you need int value of std::pair to be a part of the key. If that is a mistake, then just use std::unordered_map<string,std::map<string,int>> insted and then you can modify v->second without any problem (but that value would not be a part of the key anymore).
Note your loop is written quite ineffective way - you are invoking std::unordered_map::operator[] on every iteration. Instead you better get a reference to that element and use it:
void dfs(string u){
auto &gu = g[u];
for(auto v=gu.begin();v!=gu.end();v++){
...
The problem comes from begin() method of set. As stated in cpp ref:
Because both iterator and const_iterator are constant iterators (and may in fact be the same type), it is not possible to mutate the elements of the container through an iterator returned by any of these member functions.
This means the v is in fact const, so operator->() will be const version too. So you will not be able to change second.
BTW, a suggestion: use bool for visiting not int. it helps readability.
I am looking at a function for parsing through local addresses and am confused by the rationale behind choice for return value. The function is
bool p2p::isLocalHostAddress(bi::address const& _addressToCheck)
{
// #todo: ivp6 link-local adresses (macos), ex: fe80::1%lo0
static const set<bi::address> c_rejectAddresses = {
{bi::address_v4::from_string("127.0.0.1")},
{bi::address_v4::from_string("0.0.0.0")},
{bi::address_v6::from_string("::1")},
{bi::address_v6::from_string("::")}
};
return find(c_rejectAddresses.begin(), c_rejectAddresses.end(), _addressToCheck) != c_rejectAddresses.end();
}
I understand the actual code of the return value, whereby std::find goes through the set looking for _addressToCheck but what is the reasoning behind comparing it with the set's end iterator? Wouldn't the same logic in this case be implemented by listing the return value as
return find(c_rejectAddresses.begin(), c_rejectAddresses.end(), _addressToCheck) != NULL;
what is the reasoning behind comparing it with the set's end iterator?
std::find() takes two iterators as input, searching from the first iterator up to but not including the second iterator. If the item is found, an iterator to the item is returned. If the item is not found, the second iterator is returned. Since end() is being passed in as the second iterator, the return value has to be compared to end() to know if the address was found or not.
Wouldn't the same logic in this case be implemented by listing the return value as
return find(c_rejectAddresses.begin(), c_rejectAddresses.end(), _addressToCheck) != NULL;
No, it would not. That implies that std::find() returns a pointer, or at least an integer where 0 represents "not found". That is not the case with many containers. STL algorithms use iterators so they can be container-agnostic.
It makes the return value compatible with other algorithm functions, meaning they can be embedded into each other.
auto found = find(list.begin(), list.end(), itemToFind);
for_each(list.begin(), found, doSomething);
Potentially not the most useful example. But, it's easier to get the value from an iterator than to get an iterator from a value if need be.
I know ideally to add to a std::vector without worry I should use push_back(). However my problem set is that I need a clean code to check if the value I am entering is already in the std::vector and if not, I have to put in sequential, ascending order. To do that I am doing:
vector<Book>::iterator it;
it = std::find(books.begin(), books.end(), b);
if (it != books.end()) {
*it = b; // if b exists in books, overwrite the iterator
}
else {
vector<Book>::iterator _it;
_it = lower_bound(books.begin(), books.end(), b);
books.insert(_it, b); // here on an empty vector, _it has no values
}
The else will only run if the value b doesnt already exist in the std::vector. If this is the first value being checked against it, the else runs (since its empty) and the std::iterator is at books[0](?).
What makes me cautious about using this is that when debugging, on the insert() line, the value of _it reads "Error Reading...." for each of the members for which the std::iterator is pointing to. Now the program functions and yields anticipated results, but is it erroneously?
What you are doing works fine. However it is not the most efficient way. Using std::find doesn't take advantage of the fact that the data in the vector is sorted, it visits every element until if finds the correct one.
Instead of std::find you can use std::lower_bound from the beginning because that will find your element if it exists and if not, it will find the correct place to insert a new one.
Also it will use a binary search so it will be leaps and bounds faster than std::find. Also you don't end up finding the insertion/replacemt point twice.
Something like this should do:
void insert_or_replace(std::vector<Book>& books, Book const& b)
{
std::vector<Book>::iterator it;
it = std::lower_bound(books.begin(), books.end(), b);
if(it != books.end() && *it == b)
*it = b;
else
books.insert(it, b);
}
It is all well defined behavior.
lower_bound will return the end iterator (books.end() in your case) for an empty range or if b should come after the last element. insert will add the new element before the iterator passed to it, so this will be before end. In an empty vector, this will have the effect of adding the element to the vector.
The code is pretty fine. For an empty vector lower_bound(books.begin(), books.end(), b) will return end() iterator. Passing it to std::vector::insert will work fine.
pos - iterator before which the content will be inserted. pos may be the end() iterator
and about your worry:
What makes me cautious about using this is that when debugging, on the insert() line, the value of _it reads "Error Reading...." for each of the members for which the std::iterator is pointing to.
That's because the end() iterator points to the position following the last element, so it's invalid to deference on it (to get a nonexistent element).
So I have a vector of objects of type Player. If I try to use std::find_if on that vector and use a lambda expression that return true only if the name if the player is a name I want to check against, it will work the first time if the vector is empty (as in the iterator is nullptr), but once I add a Player object in the vector, the find_if will return an iterator filled with random, bad data.
My code:
auto player = find_if(m_playerList.begin(), m_playerList.end(), [name](Player& p)
{
return p.Name == name;
});
if (player._Ptr != nullptr)
{
// Do stuff
}
else
{
Player newPlayer;
newPlayer.Name = name;
m_playerList.push_back(newPlayer);
}
So in the first iteration of this function, the player is nullptr, because the vector didn't contain any elements. In the second iteration, if I search by the same name, it finds it in the vector, however if I search by a different name, it returns an object with mangled, random data and the check "if (player._Ptr != nullptr)" passes.
Question is, what causes this? Am I checking the "player" object properly to figure out if the find_if actually found a valid object in the vector?
The code you wrote isn't portable because it uses implementation-specific _Ptr member of vector::iterator, and therefore there are no requirements on that member to be nullptr. Change
if (player._Ptr != nullptr)
to
if (player != m_playerList.end())
I need to add, store and delete some pairs of objects, e.g. Person-Hobby. Any person can have several hobbies and several persons can have the same hobby. So, multimap is a good container, right?
Before adding a pair I need to know, if it's not added yet. As I can see here there is no standard class-method to know, if the concrete pair e.g. Peter-Football exists in the MM. Thus, I've written a method which returns a positive integer (equal to the distance between mm.begin() and pair iterator) if the pair exists and -1 otherwise.
Then I need to delete some pair. I call my find method, which returns, some positive integer. I call myMultiMap.erase(pairIndex); but the pair is not being deleted for some reason. That is my problem. Obviously the erase method needs an iterator, not the int. The question is: how do I convert an integer to an iterator?
Thanks!
UPDATE:
I've tried this c.begin() + int_value but got an error error: no match for ‘operator+’ on this line....
Not that I favour your approach, but if the int is distance between begin() and the iterator in question, you can just use
c.begin() + int_value
or
std::advance(c.begin(), int_value)
to get the iterator. The second version is needed for iterators which are not random-access-iterators.
In the interest of your personal sanity (and the program's speed), I'd suggest you return the iterator directly in some form.
There are many possible interfaces that solve this one way or the other. What I would call the "old C way" would be returning by an out parameter:
bool find_stuff(stuff, container::iterator* out_iter) {
...
if(found && out_iter)
*out_iter = found_iter;
return found;
}
use it:
container::iterator the_iter;
if(find_stuff(the_stuff, &the_iter)) ...
or
if(find_stuff(the_stuff, 0)) // if you don't need the iterator
This is not idiomatic C++, but Linus would be pleased with it.
The second possible and theoretically sound version is using something like boost::optional to return the value. This way, you return either some value or none.
boost::optional<container::iterator> find_stuff(stuff) {
...
if(found && out_iter)
return found_iter;
return boost::none;
}
Use:
boost::optional<container::iterator> found = find_stuff(the_stuff);
if(found) {
do something with *found, which is the iterator.
}
or
if(find_stuff(the_stuff)) ...
Third possible solution would be going the std::set::insert way, ie. returning a pair consisting of a flag and a value:
std::pair<bool, container::iterator> find_stuff(stuff) {
...
return std::make_pair(found, found_iter);
}
Use:
std::pair<bool, container::iterator> found = find_stuff(the_stuff);
if(found.first) ...
Consider to change your mulitmap<Person,Hoobby> to set<pair<Person,Hobby> > - then you will not have problems you have now. Or consider to change to map<Person, set<Hobby> >. Both options will not allow to insert duplicate pairs.
Use 2 sets(not multi sets) one for hobbies and one for persons, these two acts as filters so you don't add the same person twice(or hobbie). the insert opertations on these sets gives the iterator for the element that is inserted(or the "right" iterator for element if it allready was inserted). The two iterators you get from inserting into hobbies_set and person_set are now used as key and value in a multimap
Using a third set(not multi_set) for the relation instead of a multi_map, may give the advantage of not needing to check before inserting a relation if it is allready there it will not be added again, and if it's not there it will be added. In both ways it will return an iterator and bool(tells if it was allready there or if it was added)
datastructures:
typedef std::set<Hobbie> Hobbies;
typedef std::set<Person> Persons;
typedef std::pair<Hobbies::iterator,bool> HobbiesInsertRes;
typedef std::pair<Persons::iterator,bool> PersonsInsertRes;
struct Relation {
Hobbies::iterator hobbieIter;
Persons::iterator personIter;
// needed operator<(left for the as an exercies for the reader);
};
typedef set<Relation> Relations;
Hobbies hobbies;
Persons persons;
Relations relations;
insert:
HobbiesInsertRes hres = hobbies.insert(Hobbie("foo"));
PersonsInsertRes pres = persons.insert(Person("bar"));
relations.insert(Relation(hres.first, pres.first));
// adds the relation if does not exists, if it allready did exist, well you only paid the same amount of time that you would have if you would to do a check first.
lookup:
// for a concrete Person-Hobbie lookup use
relations.find(Relation(Hobbie("foo"),Person("Bar")));
// to find all Hobbies of Person X you will need to do some work.
// the easy way, iterate all elements of relations
std::vector<Hobbie> hobbiesOfX;
Persons::iterator personX = persons.find(Person("bar"));
std::for_each(relations.begin(), relations.end(), [&hobbiesOfBar, personX](Relation r){
if(r.personIter = personX)
hobbiesOfX.push_back(r.hobbieIter);
});
// other way to lookup all hobbies of person X
Persons::iterator personX = persons.find(Person("bar"));
relations.lower_bound(Relation(personX,Hobbies.begin());
relations.upper_bound(Relation(personX,Hobbies.end());
// this needs operator< on Relation to be implemented in a way that does ordering on Person first, Hobbie second.