Changing Assigned Value for STL Map [duplicate] - c++

This question already has answers here:
std::map default value
(16 answers)
Closed 8 years ago.
As I create a std::map, all the indexes are initially pointing nowhere. As soon as I call them, they allocated and assigned the value 0.
For example:
map <int, int> x;
cout << x.[31233];
The output is 0.
However, I need that all the standard assigned value to be (-1) instead of 0.
How can I change this?

After this definition
map <int, int> x;
the map has no elements. To add an element with key 31233 and value -1 you can write
x[31233] = -1;
You can not do such a way that the default value would be -1 because according to the C++ Standard relative to the subscript operator
If there is no key equivalent to x in the map, inserts value_type(x, T()) into the map
For type int int() zero initializes the corresponding object.
Otherwise you should use some insert method where you will explicitly specify an initial value.

Given a std::map<key_type, value_type>, a lookup for an arbitrary key through operator [](cont key_type&) will auto-insert value_type() for said-key if it wasn't present. In your case, value_type is int, and int() is zero-initialized, therefore zero is the result.
if you want to use a different default construct, you have options, the most extreme of which would be writing a custom allocator specialized for int and a construct member to us -1 for int value types (yuck). I think you may find it easier to simply:
std::map <int, int> x;
// load map with values...
int res = -1;
auto it = x.find(31233);
if (it != x.end())
res = x.second;
// use res here.

You should simply stop using the operator[] and instead use insert() when you intend to insert a new value. If you want to have a "default" value for things that have never been inserted, it may be better to write your own container which uses a map internally and exposes something like a get() method which returns the stored value from the map or -1 if not found. That way you avoid storing lots of -1 values that aren't needed.

Related

C++: how to initialize vector in map with non-zero size

I have a map of vectors:
std::map<int, std::vector<bool>> mymap
At times, I need to insert a new element:
auto& newvec = mymap[42];
// Add stuff to newvec
As far is I understand (and assuming that 42 is not yet in the map), this will give me newvec with length 0 (constructed as std::vector<bool> {}) which I can then extend.
Is there a way to initialize the vector to some size n right away?
(I am not concerned about performance, just wondering if there is a way to do this).
Wrapping the std::vector<bool>
You could wrap the std::vector<bool> you want to initialise in the following way:
template<size_t N>
struct myvector {
myvector(): data(N) {}
std::vector<bool> data;
};
Then, declare mymap as a map whose value type is of this wrapper type, myvector<N>, instead of std::vector<bool>. For example, for N equal to 100:
std::map<int, myvector<100>> mymap;
If the key 42 does not exist in the map yet, then:
auto& newvec = mymap[42];
will create an instance of type myvector<100>, which in turns, initialises an std::vector<bool> of size 100.
You could access the created std::vector<bool> object either through myvector's data data member or by performing reinterpret_cast<std::vector<bool>&>(newvec).
Using std::map::find() and std::map::emplace()
Another approach would be to use std::map::find() instead of std::map::operator[]() to first find out whether a given key already exists in the map by comparing its returned iterator against the one returned by std::map::end(). If the given key does not exist, then construct the vector using std::map::emplace().
In your example, the newvec could be initialized for this approach by means of the ternary opererator:
auto it = mymap.find(42); // search for an element with the key 42
bool is_key_in_map = it != mymap.end();
// if the element with the given key exists, then return it, otherwise
// construct it
auto& newvec = is_key_in_map? it->second:
mymap.emplace(42, std::vector<bool>(100, true)).first->second;
Actually, you can directly call std::map::emplace() without checking whether the given key already exists, but that will cost the useless creation of a temporary object (i.e., the std::vector<bool> object) if the key is already present in the map:
auto& newvec = mymap.emplace(42, std::vector<bool>(100, true)).first->second;
Since C++17: std::map::try_emplace()
You could use std::map::try_emplace() instead of std::map::emplace():
auto& newvec = mymap.try_emplace(42, 100, true).first->second;
This way, the temporary object, std::vector<bool>(100, true), won't be constructed if the map already contains the given key (i.e., if it already contains the key 42). This is, therefore, more efficient than using std::map::emplace(), since no temporary object will be constructed if not necessary. However, it does require C++17.
Use map::try_emplace() (or map::emplace() before C++17)
std::vector has a constructor which takes an initial size and an initial uniform value. In your case, suppose you want 125 as the initial size. With a stand-alone vector, you would use:
size_t num_bools_we_want = 1234;
std::vector<bool> my_vec(num_bools_we_want, false);
Now, std::map has a method named map::try_emplace() which forwards arguments to a constructor of the value type, which effectively allows you to choose the constructor it will use for a new element. Here's how to use it
mymap.try_emplace(42, num_bools_we_want, false);
to create a value of std::vector<bool>(num_bools_we_want, false) for the key 42. No temporary vectors are created (regardless of compiler optimizations).
The only "problem" with this solution is that try_emplace() only exists since C++17. Since you asked about C++11 - that version of the standard introduced map::emplace(), which does almost the same thing except for an issue with making a copy of the key. See this question for a discussion of the difference between emplace() and try_emplace().
You can use map::emplace member function:
mymap.emplace(42, std::vector<bool>(125, false));
to create a value of std::vector<bool>(125, false) for the key 42.
As ネロク mentions, the above emplace call will construct the value std::vector<bool>(125, false) even if the key 42 already exists in the map (this is also documented in the cppreference page I linked above). If this is to be avoided, you can first check if the value already exists using map::find and insert the value only if the key doesn't exist. That is:
if (mymap.find(42) == mymap.end()) {
mymap.emplace(42, std::vector<bool>(125, false));
}
Both map::find and map::emplace has logarithmic time complexity; hence, calling find before emplace should not hurt the performance too much in performance critical scenarios.

C++ map :first call to map[a], how to initialize map[a].second to zero?

When I use operator[] to call map[a]; while there is no key a in the map will the second element in map[a]be initialized to zero? If not, are there any good way to do it? I'm currently using if (map.find(a)) map[a]++; else map[a] = 1;. I want to use a map that is initialized to 0 and calling only map[a]++; and map[a]--;
Assuming a map with a type such as std::map<int, int> m, then a call to m[a] for a non-existent key a will insert an element with key a and value int(), that is, 0. So you can safely simplify your code to
std::map<int, int> m
....
m[a]++; // OK
In the general case, the new element insertion inserts a value initialized key object to the map. For built-in arithmetic types such as int, value initialization means zero initialization.
Assuming a std::map, if operator[] creates a new element (i.e. for a key that doesn't previously exist, then the mapped value is value initialised.
For basic types like int, that means a value of zero. For class/class types, it means default construction.

Safe to use += operator to create a new std::map entry using []?

Let's say I have a std::map<int, int>, would it be safe to do this?
std::map<int, int> m_map;
m_map[0] += 1;
If the key 0 didn't exist in the map when I do this, how would it know what value to add 1 to?
What I'm hoping is that std::map handles this by performing an = instead of += in the case where the value creates a new entry in the map. This would spare me from having to do:
std::map<int, int>::iterator it = m_map.find(0);
if(it != m_map.end()) {
it->second += 1;
}
else {
m_map[0] = 1;
}
An element inserted into a map on invoke of operator[] due to a previously-unmapped key is value-initialized. If you've not seen that syntax, consider the special meaning the () has in the following code snippet. The parens are important. They introduce an different trip down the initialization tree than default-initialization. Both are important; both are laid out by the language standard.
int i = int();
As it turns out, value initialization for scalars (including pointers) eventually succumbs to zero-initialization. Though odd looking, the prior snippet value-initializes an instance of int, which becomes zero-initialization since int is a scalar, then copies it to i. (To be fair, there will almost certainly be some eliding, but the fundamentals are as-presented).
Regardless, due to that feature, you can rest assured when you do this:
m_map[0] += 1;
or even this:
++m_map[0];
if the index wasn't mapped prior, a value-initialized element is added, which will zero-initialize for scalars, which means you'll officially starting out with zero.
It is worth mentioning that a similar activity happens for any type with an implicitly declared constructor. Whether trivial or not, something interesting happens.
struct S { int a; int b; };
std::map<int, S> mymap;
++mymap[0].a;
Is the a member mapped to 0 in our container reliably 1 after the above executes? Yes, it is. Further, consider this:
struct S { int a; std::string str; };
std::map<int, S> mymap;
++mymap[0].a;
Now S has a non-trivial implicit constructor (it has to, as it must construct str). But is the a member mapped to 0 in our container still reliably zero-initialized (and therefore 1 after the above line)? Yes, it is.
If curious about the referenced different paths of initialization, see this question and answer. Or review the C++11 standard, particularly C++11 § 8.5 Initializers, (p5,p7,p10). It is worth the read.
It's guaranteed that you'll get 1 even with the shorter snippet.
The key is that operator[] has to create the element in the map before returning a reference to it, so by the time you got to += the element already exists, and, if it had to be created now, with value zero (since elements of a map are value-initialized).
(incidentally, this is the reason why, when you use an std::map with a class type as value, it has to have a default constructor if you want to use operator[], even if you assign an object to it immediately)
Yes this is fine, new entries default to value value_type() which is 0 for int.
It still does +=, but 0 += 1 gives 1.
Map is lazily initialize with default constructor.(zero for all int)
So it 0 didn't exist in map, it would be initialized to contain value 0 and 1 would be added after += 1.
So you can use upper version of code without any worries.
Quoting Effect of calling map_obj[k] from cplusplus.com
If k matches the key of an element in the container, the function
returns a reference to its mapped value.
If k does not match the key of any element in the container, the
function inserts a new element with that key and returns a reference
to its mapped value. Notice that this always increases the container
size by one, even if no mapped value is assigned to the element (the
element is constructed using its default constructor).

is insert() necessary in a map or unordered_map?

I see a lot of examples that add items to a map or unordered_map via operator[], like so:
int main() {
unordered_map <string, int> m;
m["foo"] = 42;
cout << m["foo"] << endl;
}
Is there any reason to use the insert member function instead? It would appear they both do the same thing.
They are not.
operator[] will overwrite the value for this key, if it exists, while insert will not.
In case operator[] is used for inserting element, it is expected to be a little slower (see #MatthieuM's comment below for details), but this is not that significant here.
While std::map::insert returns std::pair< iterator, bool >, where the .second will tell you if the value is inserted or it already exists.
Regarding your comment: you cannot have 2 elements with the same key and different value. This is not a multimap.
If there's an element in the map, with the same key you're trying to insert, then:
operator[] will overwrite the existing value
std::map::insert will not do anything.* return a std::pair< iterator, bool >, where the .second will be false (saying "the new element is not inserted, as such key already exists") and the .first will point to the found element.
* I changed this thanks to the note/remark, given from #luk32; but by writing "will not do anything", I didn't mean it literally, I meant that it will not change the value of the existing element
Using insert() can help improve performance in certain situations (more specifically for std::map since search time is O(log(n)) instead of constant amortized). Take the following common example:
std::map<int, int> stuff;
// stuff is populated, possibly large:
auto iterator = stuff.find(27);
if(stuff.end() != iterator)
{
// subsequent "find", set to 15
iterator->second = 15;
}
else
{
// insert with value of 10
stuff[27] = 10;
}
The code above resulted in effectively finding the element twice. We can make that (slightly) more efficient written like this:
// try to insert 27 -> 10
auto result = stuff.insert(std::make_pair(27, 10));
// already existed
if(false == result.second)
{
// update to 15, already exists
result.first->second = 15;
}
The code above only tries to find an element once, reducing algorithmic complexity. For frequent operations, this can improve performance drastically.
The two are not equivalent. insert will not overwrite an existing value, and it returns a pair<iterator, bool>, where iterator is the location of the key, regardless of whether or not it already existed. The bool indicates whether or not the insert occurred.
operator[] effectively does a lower_bound on key. If the result of that operation is an iterator with the same key, it returns a reference to the value. If not, it inserts a new node with a default-constructed value, and then returns a reference to the value. This is why operator[] is a non-const member - it auto-vivifies the key-value if it doesn't exist. This may have performance implications if the value type is costly to construct.
Also note in C++11, we have an emplace method that works nearly identical to insert, except it constructs the key-value pair in-place from forwarded arguments, if an insert occurs.
Well I disagree with Kiril's answer to a certain degree and I think it's not full so I give mine.
According to cppreference std::map::operator[] is equivalent to a certain insert() call. By this I also think he is wrong saying the value will be overwritten.
It says: "Return value
Reference to the mapped value of the new element if no element with key key existed. Otherwise a reference to the mapped value of the existing element is returned."
So it seems it is a convenient wrapper. The insert(), however has this advantage of being overloaded, so it provides more functionality under one name.
I give a point to Kiril, that they do seem to have a bit different functionality at first glance, however IHMO the examples he provides are not equivalent to each other.
Therefore, as an example/reason to use insert I would point out, inserting many elements at once, or using hint ( Calls 3-6 in here).
So is insert() necessary in a map or unordered_map?
I would say yes. Moreover, the operator[] is not necessary as it can be emulated/implemented using insert, while the other way is impossible! It simply provides more functinality. However, writing stuff like (insert(std::make_pair(key, T())).first)->second) (after cppreference) seems cumbersome than [].
Thus, is there any reason to use the insert member function instead?
I'd say for overlapping functionality, hell no.

C++ template class map

I add the constructor and two functions to the class of my previous linked question C++ iterate through a template Map and I need help at this points:
What do you reckon this constructor does?
Adding one value at the beginning of map?
I see though in the respective key only an address as value after initializing in main. What is wrong?
The operator [] is supposed to get the values for a specific key. However I cannot use it so as to get the elements of the map in the output. Any hint?
template<class K, class V>
class template_map{
public:
template_map( V const& val) {
m_map.insert(my_map.begin(),std::make_pair(std::numeric_limits<K>::min(),val));
};
typedef typename std::map<K,V> TMap;
TMap my_map;
typedef typename TMap::const_iterator const_iterator;
const_iterator begin() const { return my_map.begin(); }
const_iterator end() const { return my_map.end(); }
V const& operator[]( K const& key ) const {
return ( --my_map.upper_bound(key) )->second;
}
...
};
int main()
{
interval_map<int,int> Map1 (10);
//Show the elements of the map?
}
Consider also that it should be a function that inserts values to the map.
What do you reckon this constructor does? Adding one value at the beginning of map?
It initialises the map so that map[x] == v for any x. The map associates intervals with values, internally storing a normal map keyed by the start of each interval; it's initialised so that the entire range of the key type maps to the initial value.
I see though in the respective key only an address as value after initializing in main. What is wrong? The operator [] is supposed to get the values for a specific key. However I cannot use it so as to get the elements of the map in the output. Any hint?
I've no idea what you're asking there. If you try, for example, cout << Map1[42] << '\n';, then your program should output 10, since that is the initial value assigned to the entire range of integers.
Consider also that it should be a function that inserts values to the map.
Since the internal map is publicly exposed, you can add a new interval to the map with
Map1.my_map.insert(std::make_pair(interval_start, value));
It might be more polite to make my_map private, and provide an insert() function to do that. You could also add a non-const overload of operator[] that inserts a new range and returns a reference to its value, something like
V & operator[](K const & key) {
V const & old_value = (--my_map.upper_bound(key))->second;
return *my_map.insert(std::make_pair(key, old_value)).first;
}
although this might not be a great idea, as you'd have to be careful that you don't accidentally insert many ranges when you only want to read the values.
My problem is how to iterate through the map to get all its elements and print them in main. It shows me an address with a value of the object initialization.
Remembering that an iterator over a map refers to a key/value pair (of type std::pair<K,V>), you should be able to iterator over the map like this:
for (auto it = Map1.begin(); it != Map1.end(); ++it) {
std::cout << it->first << " maps to " << it->second << '\n';
}
(in C++03, you'll need to write template_map<int,int>::const_iterator rather than auto).
What do you reckon this constructor does? Adding one value at the
beginning of map? I see though in the respective key only an address
as value after initializing in main. What is wrong?
It adds this one value in to the map. The iterator argument is only a hint: if the new item is to be inserted right after this position, the operation can be completed faster. Otherwise the map will need to find the right place to insert the new value as usual.
The operator [] is supposed to get the values for a specific key.
However I cannot use it so as to get the elements of the map in the
output. Any hint?
upper_bound returns iterator to the first key-value pair, where key is greater than the argument. --upper_bound therefore returns an iterator to the item, whose key is either equal or less than the queried key. If upper_bound returned map.begin(), because all keys are greater than the query, decrementing it is undefined behavior.
What you need here is the find member function. You also need to deal with the case the key is not found (map.end() is returned), e.g by throwing an exception.
Alternatively you may implement your operator[] in terms of map::operator[]. This means that the function can't be const, because map inserts a new default value if the key is not found.
The iterator in map::insert() is just a hint; essentially it doesn't mean anything in terms of the semantics of the program.
Your code inserts the value passed through the constructor argument together with the key numeric_limits<K>::min(), i.e. the smallest possible value for the given key type. This will only complile if numeric_limits is specialized for the type K.
Also note that if the key already exists, the corresponding mapped value will not be overwritten, so a corresponding insert function would be of very limited use.