std::map - Element access without exception and without insertion - c++

I have a recurrent pattern with the use of std::map.
I want to retrieve the value only when the key is present, otherwise I don't want to insert element. Currently I'm using count(key) or find(key) (which one is better? from the documentation the complexity seems to be the same) and if them returns a positive value that I access the map. However I would like to avoid the use of two operations on the map. Something like:
map<string, int> myMap;
int returnvalue;
boole result = myMap.get("key1",returnValue)
if(result){
\\ use returnValue
}
Reading the std::map documentation on cplusplus.com I found two functions for accessing map elements:
at(): which throws an excpetion if the key is not present
[]: which insert a new value if the key is not present
None of them satisfy my necessity.

Use map::find:
auto it = myMap.find(key);
if (it != myMap.end())
{
// use it->second
}
else
{
// not found
}
This part was easy. The harder problem is when you want to look up if an element exists and return it if it does, but otherwise insert a new element at that key, all without searching the map twice. For that you need to use lower_bound followed by hinted insertion.

using count() for sure the key is exists
then uses find() to get the k/v pair
if (myMap.count(key))
{
auto it = myMap.find(key)
}
else
{
// not found
}

Related

How to return a value from std::map<int, std::string> with a uint64_t key type?

Novice question, but I searched for this and couldn't find something clearly solving my issue - apologies if this is obvious.
I have defined a map which looks like this:
map<int, string> testmap = {
{ 0, "a" },
{ 1, "b" },
{ 2, "c" }
}
However, I need to retrieve a value from testmap using a uint64_t value provided by another function.
When I do testmap[my_uint64_t_value] it returns an empty string, so I think this is because it's adding my_uint64_t_value as a key and setting the value to NULL.
This is the same if I set the map type to <uint64_t, string>, at least the way I'm currently defining my keys.
However, is there a way that I either:
convert the uint64_t value to a regular int
define the map as <uint64_t, string>, and be able to define my
keys as the 'correct' type?
It seems like int type conversion isn't that common, is this something that should be avoided?
The reason why you get an empty string is std::map::operator[] returns a reference to the value if and only if it exists, otherwise it performs an insertion. I suspect you have the latter case.
You need to use std::map::find for search.
uint64_t keyToFind = 1;
if (auto iter = testmap.find(keyToFind); iter != testmap.cend())
{
// do something
std::cout << "Found!\n";
}
else { std::cout << "Not Found!\n"; }
Like #Rene mentioned in the comments, casting from uint64_t to int can cause overflow. Therefore, making the key to larger type(as per requirement) would be a good idea.
std::map<uint64_t, std::string> testmap;
As said in another answer, the [] operator of the map class will perform an insertion with a default-constructed value if the key is not present in the map.
You can first use the count method to determine if the key is present in the map before accessing it.
if(testmap.count(keyToFind))
return testmap[keyToFind];
else
report_key_not_found();
An alternative solution is to use the at method to access the value. It will throw an std::out_of_range exception if the key is not present instead of inserting a new key.

Finding an element in map by its value

I'm creating a HandleManager whose purpose is to simply map Handles (which is a typedef of long long int) to strings. The purpose is so that objects that use a Handle can also be identified via strings if it helps a user remember the object. In which case, in this map:
typedef std::unordered_map<Handle, std::string> HandleMap;
both types in the pair are keys insofar they can be used to identify anything. So far everything has compiled apart from the code which needs to get the Handle. The purpose is such that when a user allocates a string like so:
handle("myHandle");
A Handle is generated randomly and then the string passed is paired with it in the foresaid map. What I want now is to be able to get the Handle that is paired with the string based on the string that is passed:
Handle HandleManager::id(const std::string &name)
{
HandleMap::iterator it = pHandles.find(name);
if (it != pHandles.end())
return it->first;
return -1;
}
But for some weird reason the compiler complains about this:
HandleManager.cpp:48:45: error: no matching function for call to ‘std::unordered_map<long long int, std::basic_string<char> >::find(const string&)’
In the foresaid map, the string is the value and the Handle is the key. So how can I get the key from the unordered_map based on the value contained therein?
You can use the member function find to search for key only. To search for a value, you can use a std::find_if with a lambda function (if you use C++11), or to traverse the map (ok in previous C++ version):
for (HandleMap::const_iterator it = map.begin(); it != map.end(); ++it) {
if (it->second == name) return it->first;
}
// or value not found
On the other hand, if searching for a value is a very common operation, you may want to have two maps: std::unordered_map<Handle, std::string> and std::unordered_map<std::string, Handle>. In that case, you have to make sure you perform insertions, deletions, etc. in both maps to keep then synchronized.
std::unordered_map::find operates on the key, not the value. You can use std::find_if:
Handle HandleManager::id(const std::string &name)
{
auto it = std::find_if(std::begin(pHandles), std::end(pHandles),
[](auto&& p) { return p->second == name; });
if (it == std::end(pHandles))
return -1;
return it->first
}
Note that auto, std::begin, std::end and lambdas are C++11 and generic lambdas are C++14, so substitute those out if you're stuck with an old compiler.
But for some weird reason the compiler complains about this:
Of course it does, the find function is for lookup up by key and you're not doing that.
To find a value you need to visit every element until you find it (or use a bidirectional map which maps values back to keys, e.g. Boost.Bimap).
Based on answer from #TartanLlama (*):
Handle HandleManager::id(const std::string & name) {
auto iter = std::find_if(std::begin(pHandles), std::end(pHandles),
[& name](auto && pair) {
return pair.second == name;
});
if (it == std::end(pHandles)) {
return -1;
}
return it->first;
}
(*): Because it doesn't seem possible to format code in comments.

How to take unique values from file with vector in CPP using map?

I have vector of some data type (Let's say-int) and I need to push back only unique values from the file? I am new to use STL. So i don't know how can i do it using map as i read that map only takes unique values. If I simply push back, then it will take all the values irrespective of its uniqueness.
The correct container to use for unique values is either std::set or std::unordered_set:
std::set<int> s;
s.insert(4); // s has size 1
s.insert(5); // s has size 2
s.insert(4); // s still has size 2
If you want to use vector, you'd have to maintain it sorted, which is a lot more code and work, and doesn't have the nice characteristic of set that everybody knows the contents are unique:
void add_value(std::vector<int>& v, int value) {
// do a binary search to find value
std::vector<int>::iterator it = std::lower_bound(v.begin(), v.end(), value);
if (it != v.end() && *it == value) {
// duplicate - do nothing
}
else {
// insert our value here
v.insert(it, value);
}
}
... or I guess you could delete the duplicates at the end using a rarely-used algorithm (std::unique) that will probably raise some eyebrows:
void uniqify(std::vector<int>& v) {
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
}
[UPDATE] It has been pointed out to me that I completely misunderstood your question - and that you may have been looking for just which values occur exactly once - not a list of which values occur without duplicate. For that, the correct container to use is either a std::map or std::unordered_map - so you can associate a count with a particular key:
std::map<int, int> keyCounts;
int value;
while (fileStream >> value) { // or whatever
++keyCounts[value]; // operator[] gives us a reference to the value
// if it wasn't present before, it'll insert a default
// one - which for int is zero - so this handles
// both cases correctly
}
// Now, any key with value 1 is a unique key
// what you want to do with them is up to you
// e.g., let's put it in a vector
std::vector<int> uniq;
uniq.reserve(keyCounts.size());
for (std::map<int, int>::iterator it = keyCounts.begin(); it != keyCounts.end(); ++it)
{
if (it->second == 1) {
uniq.push_back(it->first);
}
}
A std::map will let you handle a mapping of unique keys to some values (which may or may not be unique). Math-wise, You may see it as a surjective function from the set of keys to the set of values of your dataset.
If your goal is to keep unique indices (or keys), then std::map is what you need. Otherwise, use std::set to store unique values.
Now, to keep only unique values from your dataset, you basically want to remove values which appear more than once. The simplest algorithm is to add values from the file as keys in a map, with its corresponding value being a counter for the number of occurrences of that entry in the file. Initialize a counter to 1 the first time the value is met in the file, and increment it each time it is met again. After having parsed the whole file, simply keep the keys whose values are exactly 1.
Counting the values:
template <typename key>
void count(std::istream &is, std::map<key,int> &map){
while (!is.eof() && is.good()){
key << is;
auto it = map.find(key);
if (it == map.end())
map[key] = 1;
else (*it)++;
}
}
The above assumes that the << has been overloaded to extract values from the stream sequentially. You will have to adapt the algorithm to fit your own way of parsing the data.
Filtering the resulting map to keep unique values can be achieved with std::remove_if and a function returning true when the counter is above 1:
The function:
bool duplicate (std::const_iterator<int> &it){ return *it > 1;}
The map filtering:
std::remove_if (map.begin(), map.end(), duplicate);

Get all the keys which matches a query in a map

Say I have more than one key with the same value in a map. Then in that case how do I retrieve all keys that matches a query.
Or, Is there any possibility to tell find operation to search after a specific value.
I am using an std::map, C++.
Would something like this work for you:
void FindKeysWithValue(Value aValue, list<Key>& aList)
{
aList.clear();
for_each(iMap.begin(), iMap.end(), [&] (const pair<Key, Value>& aPair)
{
if (aPair.second == aValue)
{
aList.push_back(aPair.first);
}
});
}
The associative containers probably won't help you too much because for std::map<K, V> the key happens to be unique and chances that your chosen query matches the ordering relation you used may not be too high. If the order matches, you can use the std::map<K, V> members lower_bound() and upper_bound(). For std::multimap<K, V> you can also use equal_range().
In general, i.e., if you query isn't really related to the order, you can use std::copy_if() to get a sequence of objects matching a predicate:
Other other;
// ...
std::vector<Other::value_type> matches;
std::copy_if(other.begin(), other.end(),
std::back_inserter(matches), predicate);
When copying the elements is too expensive, you should probably consider using std:find_if() instead:
for (auto it(other.begin());
other.end() != (it = std::find_if(it, other.end(), predicate));
++it) {
// do something with it
}
The only way is to iterate over map.
this link may be useful: Reverse map lookup
Provided you want quick access and you don't mind using some more space, then you maintain another map that gets stored as value, key. In your case, you would need to handle the duplicate values (that you will be storing as keys).
Not a great idea but definitely an option.
A map is meant for efficient lookup of keys. Lookup based on values is not efficient, and you basically have to iterate through the map, extracting matches yourself:
for(map<A,B>::iterator i = m.begin(); i != m.end(); i++)
if(i->second == foo)
you_found_a_match();
If you intend to do this often, you can build up a multimap mapping the other way, so you can efficiently perform a value-based lookup:
multimap<B,A> reverse;
for(map<A,B>::iterator i = m.begin(); i != m.end(); i++)
reverse.insert(pair<B,A>(i->second,i->first));
You can now easily find the keys with a given value value:
matches = reverse.equal_range(value);
for(multimap<B,A>::iterator i = matches.first; i != matches.second; i++)
A & key = i->second;
If these maps aren't going to grow continuously, it may be more efficient to simply maintain a vector > instead, define a comparator for it based on the value, and use equal_range on that instead.

C++ find and erase a multimap element

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.