How does insertion in an unordered_map in C++ work? - c++

int main()
{
auto n=0, sockNumber=0, pairs=0;
unordered_map<int, int> numberOfPairs;
cin >> n;
for(int i=0; i<n; ++i)
{
cin >> sockNumber;
numberOfPairs.insert({sockNumber, 0}); // >>>>>> HERE <<<<<<
numberOfPairs.at(sockNumber) += 1;
if(numberOfPairs.at(sockNumber) % 2 == 0)
{
pairs += 1;
}
}
cout << pairs;
return 0;
}
This code counts the number of pairs in the given input and prints it. I want to know how the insert method of an unordered_map works. Every time I see a number, I've inserted it with a value '0'.
Does the insert method skip inserting the value '0' when it sees the same number again? How does it work?
Input -
9
10 20 20 10 10 30 50 10 20
Output -
3

Does the insert method skip inserting the value '0' when it sees the
same number again?
Yes, it does.
From the cpp.reference.com unordered_map :
Unordered map is an associative container that contains key-value
pairs with unique keys. Search, insertion, and removal of elements
have average constant-time complexity.
And from the cpp.reference.com unordered_map::insert :
Inserts element(s) into the container, if the container doesn't
already contain an element with an equivalent key.
How does it work?
I suppose that certain work principles depend much on the particular STL implementation.
Basically unordered_map is implemented as a hash table where elements are organized into the buckets corresponding to the same hash. When you try to insert a key-value pair key hash is computed. If there is no such hash in the hash table or there is no such key-value pair in the bucket corresponding to the computed hash then the new pair is inserted into the unordered_map.

A std::unordered_map holds unique keys as values. If you want to keep inserting the same key, then use std::unordered_multimap.
Also, you should realize that std::unordered_map::insert returns a value that denotes whether the insertion was successful.
if ( !numberOfPairs.insert({sockNumber, 0}).second )
{
// insertion didn't work
}
You could have used the above to confirm that the item wasn't inserted, since the same key existed already in the map.

unordered_map does not allow key duplicates, so if you are trying to use the .insert() method to insert the same key it will fail and skip that operation. However if you use unorderedMap[key] = value to insert a duplicate key, it will not skip but updating the value matching the key to the new value.

Related

C++ map showing values as 0 for all keys

I am inserting number from a vector in map with key as the number and value as it's (index + 1).
But when I print the contents of the map, value being shown is 0 though I pass interger i.
// taking input of n integers in vector s;
vector<int> s;
for(int i=0;i<n;i++){
int tmp;cin>>tmp;
s.push_back(tmp);
}
//creating map int to int
map<int,int> m;
bool done = false;
for(int i=1;i<=s.size();i++){
//check if number already in map
if (m[s[i-1]]!=0){
if (i-m[s[i-1]]>1){
done = true;
break;
}
}
// if number was not in map then insert the number and it's index + 1
else{
m.insert({s[i-1],i});
}
}
for(auto it=m.begin();it!=m.end();it++){
cout<<endl<<it->first<<": "<<it->second<<endl;
}
For input
n = 3
and numbers as
1 2 1 in vector s, I expect the output to be
1: 1
2: 2
but output is
1: 0
2: 0
Why 0? What's wrong?
Your code block following the comment:
// check if number already in map
is logically faulty, because operator[] will actually insert an element, using value initialisation(a), if it does not currently exist.
If you were to instead use:
if (m.find(s[i-1]) != m.end())
that would get rid of this problem.
(a) I believe(b) that value initialisation for classes involve one of the constructors; for arrays, value initialisation for each item in the array; and, for other types (this scenario), zero initialisation. That would mean using your method creates an entry for your key, with a zero value, and returns that zero value
It would then move to the else block (because the value is zero) and try to do the insert. However, this snippet from the standard (C++20, [map.modifiers] discussing insert) means that nothing happens:
If the map already contains an element whose key is equivalent to k, there is no effect.
(b) Though, as my kids will point out frequently, and without much prompting, I've been wrong before :-)
std::map::operator[] will create a default element if it doesn't exist. Because you do m[s[i-1]] in the if condition, m.insert({s[i-1],i}); in else branch will always fail.
To check if key is already present in map use either find(), count() or contains() (if your compiler supports C++20)
//either will work instead of `if (m[s[i-1]]!=0)`
if (m.find(s[i-1]) != m.end())
if (m.count(s[i-1]) == 1)
if (m.contains(s[i-1])) //C++20

C++ Map get first element when second element is X

I have a C++ map called buttonValues as shown below.
map<int, int> buttonValues;
I put some data into my map as shown below.
buttonValues.insert(std::pair<int, int>(0, 1));
buttonValues.insert(std::pair<int, int>(1, 3));
buttonValues.insert(std::pair<int, int>(2, 0));
What I want to do is search for value 0 in the second column and if 0 is found in the second column, return the value in the first column. In this example, the value I would like to be returned is 2. So far I believe I can search for 0 in the second column with this:
buttonValues.find(0)->second
However, how do I get the value corresponding in the first column?
Thanks
Calum
buttonValues.find(0)->second will give you the value ("2nd column") corresponding to key 0. In your example, it will return 1. You need to iterate over the map and look for values = 0 and then return the key:
for (const auto& keyval : buttonValues) // Look at each key-value pair
{
if (keyval.second == 0) // If the value is 0...
{
return keyval.first; // ...return the first element in the pair
}
}
You can put this in a function. Note that a map has unique keys but not necessarily unique values. So you should probably handle the case where you have multiple keys for which the value is 0.
Something like this:
for ( auto X : map_name )
if ( X.second == 0 )
return X.first;
std::pair<> holds values of first and second columns in your map. You can just iterate through all pairs and check second values for what you want.
Actually from performance's perspective, it is not suggested to search key by value from a map, the time complexity would be linear time O(N). If you search value by key from a map, it would be 'O(logN)'. You can consider to build a reverse map or multimap, or even unordered_map / unordered_multimap depends on your use case.

How to order the elements of a map using its values if keys are same?

I have a situation where I have to store marks of some students in descending order. So created a map<marks,string>. As a map stores data in the form of heap, always highest mark will be on top. But the problem arises when I have same mark for two students and I have to then rank them on the basis of their name (Alphabetical order considering their name's first letter).
What I did: I separated the marks and names into another map this time with key as name and mark as value. And then printed the new map in the reverse way. And then continued from where I left in the old map. But this requires creation of extra map and involves lot of processing.
My question Is there a better way to do this?
You could create a map<marks, list<string> > or something similar to store all names for one mark there. Lists come with a sort method, so you can sort all lists that have more than one entry.
If you insert them all at a time, you can
create a list if the key is not present
append the name to the mark's list
iterate over all keys
sort the value if larger than 1
print.
What I would do is to have a map like this :
map<marks, vector<string>>
As this, each mark is binded to a sorted array of students. Everytime you want to insert a new student into a vector, you simply need to find the right index to keep your vector sorted (Dichotomic search is the fastest)
You can also use list<string> instead of vector
You may just use set<pair<mark, string>/*optional appropriate-comparator*/>. It'll sort using marks first, then names.
It'll still be O(log n) for sorted insertion contrary to sorted vector solution
Basically you need a Map that can store duplicate keys(int) in non-ascending order and values(string) in non-descending order.
I have a situation where I have to store marks of some students in descending order.
Use std::greater so that the keys will be stored in descending order:
std::map<int, string, std::greater<int> > m;
But the problem arises when I have same mark for two students.
Use Multimap instead of Map. Multimap stores duplicate keys and that is its specialty.
std::multimap<int, string, std::greater<int> > m;
I have to then rank them on the basis of their name (Alphabetical order considering their name's first letter).
Store the value of identical-keys in map in a vector > sort the vector > insert the sorted vector back into map.
Note that, inside multimap, you don't need to worry about changing the keys or its order. Values are the only ones whose order is going to be changed (that too only within same-valued-key).
Demo
multimap<int, string, greater<int> >:: iterator it = m.begin(), st; //Stores duplicate keys(marks).
vector<string> vec;
int lastKey = INT_MAX;
while(it != m.end())
{
if(it->first < lastKey)
{
//Sort the Names
sort(vec.begin(), vec.end());
for(int i = 0; i < vec.size(); i++){
st->second = vec[i];
st++;
}
st = it;
vec.clear();
}
vec.push_back(it->second); //Insert name
lastKey = it->first; //marks of last inserted name.
it++;
}
//Do same for the last(lowest valued key in map) key.
sort(vec.begin(), vec.end());
for(int i = 0; i < vec.size(); i++){
st->second = vec[i];
st++;
}

Vector of pairs to map

I have a little problem.
I have a vector of pairs patternOccurences. The pairs are <string,int>, where string is the pattern(name) and int the index where it appears. My problem is that patternOccurences has multiple pairs with the same .first(same pattern) but different int values.
For example: The vector has 10 entries. 5 of pattern "a" and 5 of pattern "b". all have different indices. Now i want to have a map (or something similar) so that i have a vector/list with each pattern(in my example "a" and "b") as a key and a vector of their indices as the value. The indices are in the different pairs in my vector of pairs and i want all indices for pattern "a" in a int vector as value for key "a".
I tried the following:
std::map<std::string,std::vector<int>> occ;
for(int i = 0;i<patternOccurences.size();i++){
if(occ.find(patternOccurences.at(i).first)==occ.end()){
occ[patternOccurences.at(i).first]=std::vector<int>(patternOccurences.at(i).second);
}
else{
occ[patternOccurences.at(i).first].push_back(patternOccurences.at(i).second);
}
}
patternOccurences is the vector of pairs and occ the desired map. First i check if there is already an entry for the string(pattern) and if not i create one with a vector as value. If there is already one I try to push_back the vector with the index. However it doesnt seem to be working right. For the first pattern i get a vector with 0 only as values and for the second there are only 3 indices which are right and the other ones are 0 as well.
I hope you can help me.
Kazoooie
You are calling the constructor for the vector in the wrong way:
std::vector<int>(patternOccurences.at(i).second);
This creates a vector with N default constructed elements, not a vector with one element with value N. You need:
std::vector<int>(1, patternOccurences.at(i).second);
That should fix the problem, but your code doesn't have to be that complicated. The following would work just fine:
for(int i = 0;i<patternOccurences.size();i++){
occ[patternOccurences.at(i).first].push_back(patternOccurences.at(i).second);
}
or with C++11, the even simpler:
for(auto& p:patternOccurences) {
occ[p.first].push_back(p.second);
}
What you are asking for already exists in STL and it's called std::multimap (and std::unordered_multimap).
Take a look here. Basically it's a map which allows more values to have the same key.
std::multimap<std::string, int> occ;
occ.insert(std::pair<std::string,int>("foo", 5));
occ.insert(std::pair<std::string,int>("foo", 10));
std::pair<std::multimap<std::string,int>::iterator, std::multimap<std::string,int>::iterator> group = occ.equal_range("foo");
std::multimap<std::string,int>::iterator it;
for (it = ret.first; it != ret.second; ++it) {
..
}
Change this statement
occ[patternOccurences.at(i).first]=std::vector<int>(patternOccurences.at(i).second);
to
occ[patternOccurences.at(i).first]=std::vector<int>(1, patternOccurences.at(i).second);

C++ Multimap manipulation

I have created a multimap as I have repeating keys. But I want do an efficient manipulation so that I can generate a new multimap with subsequent higher keys aligned. This is what I mean:
This is what I have:
key values
11 qwer
11 mfiri
21 iernr
21 ghfnfjf
43 dnvfrf
This is what I want to achive
key values
11 qwer,iernr
11 mfiri,iernr
21 iernr,dnvfrf
21 ghfnfjf,dnvfrf
43 dnvfrf
I have about 10 million entries so I am looking for something efficient.
In above value "qwer,iernr" is one string.
Here's a simple way to do it:
auto cur = map.begin();
auto next = map.upper_bound(cur->first);
for(; next != map.end(); next = map.upper_bound(cur->first))
{
for(; cur != next; ++cur)
{
cur->second += ", ";
cur->second += next->second;
}
}
... given a std::multimap<int, std::string> map;
However, any operation transforming 10m+ elements isn't going to be super fast.
Looks like straight-forward way would work fine. Map elements will be laid out in ascending order (assuming compare operator suits you). So just going through the equal ranges and modifying them with value of the element just after the range will do what you want.
Clone map (if you need the original), take first element, get equal_range() for its key, modify values with value of second iterator in the range (unless it is the last one). Get equal_range() for the key of second iterator. Repeat.
agree with Eugene ! also see following reference in terms of equal_range()
stl::multimap - how do i get groups of data?
To do this, you need to simply iterate through the map, while building the new map in order.
You can do this in two levels:
for (auto it=map.cbegin(); it != map.cend(); )
{
// The inner loop is over all entries having the same key
auto next_key_it=find_next_key_after(it);
for (; it != next_key_it; ++it) {
new_map.emplace_hint(new_map.end(), it->first, new_value(it->second, next_key_it));
}
}
The new_value function (or lambda) does the value transformation (or not, if the second parameter is map.end()).
The find_next_key_after(it) function returns the same as map.upper_bound(it->first), but could also be implemented as linear search for the first entry with different key.
It depends on your (expected) key distribution, which to use - if keys repeat a small, limited number of times, linear search is better; if the number of different keys is limited, with large equal key ranges, then upper_bound may be better.
For guaranteed complexity, linear search is better: The whole algorithm then has O(n) complexity. Which is as efficient as you can get.