Arpa's blog

By Arpa, history, 9 years ago, In English

UPD: Tricks to make unordered_map faster added.

Hi!

What is unordered_map?

It is a data structure like map but it is more than 4 times faster than map.you can use it in C++11 with including #include<unordered_map>.for example:

#include<unordered_map>
using namespace std;
int main(){
  unordered_map<int,int>mp;
  mp[5]=12;
  mp[4]=14;
  cout<<mp[5]<<' '<<mp[4]<<endl;//prints: 12 14
}

Lets explain it more.

How it works?

Focus on unordered_set for simplify.You can imagine that it has vector of vector like vector<vector<type> > mp. Every time you insert value V in that, it calculate its hash(I will explain how it works), let hash(V)=K; it inserts V into mp[K] (formally it calls mp[K].push_back(V)).When you call mp.count(V) it searchs for V in mp[K].

map VS unordered_map (and set VS unordered_set)

1-unordered_map is more than 4 times faster

Focus on problem 527E - Страсти в дата-центре, it seems that it is good to use unordered_map instead of map.

My submission with map: 14269000 Time:484 MS.

My submission with unordered_map: 14269381 Time:358 MS.

Another good sample to show how fast is unordered_map is problem 178C3 - Бобер и разрешение коллизий:

My submission with map: 15781810 Time limit exceeded on test 36 .

My submission with unordered_map: 15774685 Accepted (Time:966 MS).

2-unordered_set (and unordered_map) is not sorted

Please note that set (and map) is sorted, for example *(s.begin()) is always smallest number in the set; but in unordered_set it isn't. similarly there isn't lower_bound and upper_bound in unordered_set (and unordered_map similarly).

Creating hash function for structs

Now, one other problem remains, you can try to compile this code:

unordered_map<pair<int,int>,int>mp;

You will get Compilation Error! Why? See this page for unordered_map supported types. For unsupported types, you have to create your own hash function for use. For example lets see how we can create a hash function for pair<int,int>.

As you know any int value is between -2^31+1 to 2^31-1.so if we create function that for every pair<int,int> returns distinct value in type size_t(alias of unsigned int), it will be done. It is pretty easy: x.first^(x.second<<32) is good. but be careful about overflow ;for having good hash function we use hash<long long>.The code is looks like this:

struct HASH{
  size_t operator()(const pair<int,int>&x)const{
    return hash<long long>()(((long long)x.first)^(((long long)x.second)<<32));
  }
};
unordered_map<pair<int,int>,int,HASH>mp;

Now you have a unordered_map of pair<int,int> (it isn't problem what is second member, in example it was int).Creating hash function for other structs is same.

How to test unordered_map is faster than map?

You can test that for N=10^6, unordered_set(unordered_map) is more than 4 times faster than set(map) ;with this code:(compile code with command: g++ file.cpp -std=c++11 -O2)

#include <bits/stdc++.h>
using namespace std;
unordered_set<int>s;//replace it with set for test.
int main(){
  auto T=clock();
  s.reserve(32768); //updated !
  s.max_load_factor(0.25); //updated !
  for(int i=0;i<1000000;i++)
    s.insert(i);
  cout<<double(clock()-T)/CLOCKS_PER_SEC<<'\n';
}

Note1: Let your hash function H(V), it is better that H(V) returns distinct value for every distinct V, it makes unordered_map faster; but if you can't do that it doesn't problem. The only problem is that unordered_map becomes slower(however it is better than map).

Note2: Please be careful about your hash function time complexly! it is fun to use this hash function:

struct HASH{
  size_t operator()(const pair<int,int>&x)const{
    size_t ans=0;
    for(int i=0;i<x.first;i++)
      ans+=x.second;
    return ans;
  }
};

UPD I will explain hash<type> in my next post.

UPD It seems that sometimes unordered_map becames so slow.but it can improve with this two lines of code:

unordered_map<int,int>mp;
mp.reserve(1024);
mp.max_load_factor(0.25);

With this two lines unordered_map become about 10 times faster. You can replace 1024 with another suitable power of two.(it depends on number of insert s you will do).

  • Vote: I like it
  • +47
  • Vote: I do not like it

| Write comment?
»
9 years ago, # |
  Vote: I like it 0 Vote: I do not like it

Thank you so much! I had no idea about this. From now I'll use it as much as possible :)

  • »
    »
    9 years ago, # ^ |
    Rev. 2   Vote: I like it -6 Vote: I do not like it

    you can use unordered_set/map when you don't need order of members.

    for example one of unordered_set applications is when you are storing all of the settings of one vector (for example); you can store them in unordered_set.for example:

    unordered_set<vector<int> >all;
    int count(vector<int>v){
      if(all.count(v))
        return 0;
      all.insert(v);
      ...
    }
    

    you will never check the same vectors with that trick!

»
9 years ago, # |
  Vote: I like it 0 Vote: I do not like it

Unordered_map isn't faster than map when count of elements isn't much. Sorry for bad eng :)

  • »
    »
    9 years ago, # ^ |
    Rev. 2   Vote: I like it +1 Vote: I do not like it

    if N is about 100 ;map has equal time with unordred_map.

    but you can test it when N is about 10^6. it seems that unordered_map is about 4 times faster than map.

    • »
      »
      »
      5 years ago, # ^ |
      Rev. 3   Vote: I like it 0 Vote: I do not like it

      Hi, I need a small clarification. I was just solving this problem. I made submission using both unordered_map and map. But when I include the reserve and max_load_factor, my code gets AC I don't understand why the normal unordered_map gives TLE when N is about 1e5 when it's supposed to be faster than map. Could you please help me with this? Is it because of "sometimes unordered_map becomes so slow" ?

      • »
        »
        »
        »
        4 years ago, # ^ |
        Rev. 2   Vote: I like it 0 Vote: I do not like it

        Hey AishwaryaR

        When the capacity is not reserved beforehand, it takes capacity as 16 and creates a hashtable on this.

        Everytime the number of elements increase than threshold, it doubles this capacity and rehashes all the existing keys.

        Reserving an approximate capacity before hand avoids this rehashing and dynamic space allocation, in turn making the program more efficient and reducing the runtime.

»
9 years ago, # |
  Vote: I like it +28 Vote: I do not like it
    size_t operator()(const pair<int,int>&x)const{
        return ((long long)x.first)^(((long long)x.second)<<32);
    }

Most competitive programming environments are still 32-bit. So, by doing ^(((long long)x.second)<<32) and then implicitly casting to size_t, you are effectively discarding x.second. Now, the hash depends only on x.first.

Here is the code to check that:

#include <bits/stdc++.h>

using namespace std;

struct HASH{
  size_t operator()(const pair<int,int>&x)const{
    return ((long long)x.first)^(((long long)x.second)<<32);
  }
};

unordered_map<pair<int,int>,int,HASH>m;

int main(){
  auto T=clock();
  for(int i=0;i<20000;i++)
    m[make_pair (1, i)] = i;
  cout<<double(clock()-T)/CLOCKS_PER_SEC<<'\n';
}

Note make_pair (1, i). The above test takes more than a second locally when compiled in 32-bit mode. You can run a custom test in any Codeforces past contest and check for yourself.

  • »
    »
    9 years ago, # ^ |
    Rev. 5   Vote: I like it -23 Vote: I do not like it

    Post edited.

    • »
      »
      »
      9 years ago, # ^ |
        Vote: I like it +11 Vote: I do not like it

      Sorry, I don't get what you are saying.

      I think you have to work on your math. 858 is smaller than 1000; isn't it?

      So what? That's still on the order of a second.

      Note that we insert 20,000 elements, not a million like you do. If inserting twenty thousand, again, elements takes on the order of a second, that's quadratic behavior, or at least definitely not linear. The constant 20,000 is used instead of your code's 1,000,000 so that we can actually see the result in reasonable time. One can make it 1,000,000 and watch it time out (I, for one, wasn't patient enough to let it finish).

      Also, note that changing make_pair (1, i) to make_pair (i, i) makes the program run in an instant (less than 100 ms).


      To summarize again:

      1. Codeforces checks your solutions in a 32-bit environment.

      2. So, size_t is 32 bits long.

      3. So, for every possible X and Y, the result of X ^ (((long long)Y)<<32) converted to size_t is just X.

      4. So, the hashes for pairs (1, 0), (1, 1), (1, 2), (1, 3), ..., (1, 999999) will be equal, and unordered_map will have trouble storing them.

      • »
        »
        »
        »
        9 years ago, # ^ |
        Rev. 2   Vote: I like it 0 Vote: I do not like it

        I have interested! thank you.

        I have changed my hash function, you can see the new one.

        I have mistaken, size_t is alias of unsigned int, no unsigned long long int.

        • »
          »
          »
          »
          »
          9 years ago, # ^ |
            Vote: I like it 0 Vote: I do not like it

          The new hash approach perhaps does something like x * 37 + y anyway, so why not do that explicitly?

          struct HASH{
            size_t operator()(const pair<int,int>&x)const{
              return (size_t) x.first * 37U + (size_t) x.second;
            }
          };
          

          On a side note, I'm surprised that a language as mature as C++ (11) does not define a default hashing method for library containers such as pair or vector.

          • »
            »
            »
            »
            »
            »
            9 years ago, # ^ |
            Rev. 2   Vote: I like it +5 Vote: I do not like it

            It has one for vector: hash<vector<int> >.

            You can use it for pair, first create a vector of {x.first,x.second} and use hash<vector<int> >.

            • »
              »
              »
              »
              »
              »
              »
              9 years ago, # ^ |
                Vote: I like it 0 Vote: I do not like it

              Can you please elaborate?

              I try the following:

              #include <bits/stdc++.h>
              using namespace std;
              unordered_set <vector<int>, hash<vector<int> > () > s;
              int main() {s.insert ({1, 2, 3});}
              

              And it does not compile (MinGW GCC 4.8.1 and 5.1.0), throwing some 100+ lines of error messages at me, basically stating that I misuse hash<vector <int> >. Removing () after hash<vector<int> > does not help.

              Googling for a few minutes did not help me either. I only found a reference stating that it is not implemented in the library, and a few implementations for custom types.

              • »
                »
                »
                »
                »
                »
                »
                »
                9 years ago, # ^ |
                  Vote: I like it 0 Vote: I do not like it

                You can create it like this:

                namespace std{
                    template<>struct hash<vector<int> >{
                        size_t operator()(vector<int> const& v) const{
                          unsigned long long h=0;
                          for(auto &x:v)
                            h<<=32,h^=hash<int>()(x),h=hash<long long>()(h);
                          return h;
                        }
                    };
                }
                
                
                • »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  9 years ago, # ^ |
                    Vote: I like it 0 Vote: I do not like it

                  So, what you are saying is that there is in fact no built-in hash<vector<int> > after all? Then my point still stands.

                  I won't deny that almost anything is possible with C++. I'm just surprised some trivial pieces of that work aren't in the library already.

  • »
    »
    4 years ago, # ^ |
      Vote: I like it +10 Vote: I do not like it

    Hey Gassa! Would you recommend same struct HASH when the order of elements in my pair doesn't matter? for example, in my case pair (1, 2) can be considered same as (2, 1). Is there any faster way for that?

    • »
      »
      »
      4 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      When you store the pairs, always make it such that pair.first <= pair.second holds.

    • »
      »
      »
      4 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      In fact, my point above was that I don't like the very idea of C++ not having a standard hash of pair in the library, and the 95%+ horrendous implementations of pair hashing that ensue. Plentiful examples around or on StackOverflow.

      In particular, no, I don't recommend hashing by just XORing the elements of the pair, as there are naturally occurring cases when it results in suboptimal behaviour.

      As for hashing a pair where the order of elements doesn't matter, I'd do that explicitly at first opportunity: when constructing the pair. So that (a, b) becomes (min(a,b), max(a,b)) right away. And then use standard pairs.

»
9 years ago, # |
  Vote: I like it 0 Vote: I do not like it

I run these two versions of map and unordered_map on custome test with GNU G++11 5.1.0. I see that unordered_map is slower than map

Version of map:

#include <bits/stdc++.h>

using namespace std;

map<pair<int,int>,int>m;

int main(){
  auto T=clock();
  for(int i=0;i<1000000;i++)
    m[make_pair (1, i)] = i;
  cout<<double(clock()-T)/CLOCKS_PER_SEC<<'\n';
}

Version of unordered_map:

#include <bits/stdc++.h>

using namespace std;

struct HASH{
  size_t operator()(const pair<int,int>&x)const{
    return ((long long)x.first)^(((long long)x.second)<<32);
  }
};

unordered_map<pair<int,int>,int,HASH>m;

int main(){
  auto T=clock();
  for(int i=0;i<1000000;i++)
    m[make_pair (1, i)] = i;
  cout<<double(clock()-T)/CLOCKS_PER_SEC<<'\n';
}

Why is that?

  • »
    »
    9 years ago, # ^ |
    Rev. 7   Vote: I like it 0 Vote: I do not like it

    On Intel® Core™ i3 CPU M 390 @ 2.67GHz × 4 :

    map : 2469 MS

    unordered_map: 485 MS

    On CodeForces:

    map: 2469 MS

    unordered_map : 1885 MS

    • »
      »
      »
      6 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      hey @Arpa can u explain me the struct HASH code? thanks in advance.....

      • »
        »
        »
        »
        6 years ago, # ^ |
          Vote: I like it 0 Vote: I do not like it

        You should implement operator () for this struct. It takes an object and returns size_t (alias of unsigned int). You should write it in a way such that minimize the number of conflicts.

  • »
    »
    9 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Add this two lines:

    m.reserve(4096);
    m.max_load_factor(0.25);
    
»
9 years ago, # |
  Vote: I like it 0 Vote: I do not like it

Auto comment: topic has been updated by Arpa (previous revision, new revision, compare).

»
9 years ago, # |
Rev. 3   Vote: I like it 0 Vote: I do not like it

What does this mean ? s.max_load_factor(0.25);

And which library it uses ?

  • »
    »
    9 years ago, # ^ |
    Rev. 3   Vote: I like it 0 Vote: I do not like it

    The load factor is the ratio between the number of elements in the container (its size) and the number of buckets (bucket_count). (source)

    it uses <unordered_map>(or <unordered_set>) for sure.

»
8 years ago, # |
  Vote: I like it +5 Vote: I do not like it

Hi can anyone explain, why map is more than 10 times faster in this case? I could not find any reason for this. any help on this matter would be highy appreciated. I also tried adding the 2 lines of code mentioned above to make unordered_map faster but with no luck. 21740384 and 21740655

  • »
    »
    8 years ago, # ^ |
      Vote: I like it +5 Vote: I do not like it

    Because map has a logarithmic access time on size always, on the other hand, unordered_map in average has a constant access time, but in worst case it's just linear in size.

    • »
      »
      »
      8 years ago, # ^ |
      Rev. 3   Vote: I like it 0 Vote: I do not like it

      Thanks for the quick reply! I am aware of the worst case complexity of unordered map. However, usually most of the times unordered map is observed to be faster. Can you share any further insights on deciding between unordered map and map. And also what is making unordered map slower than map in the above case I presented? i,e.. what kinds of data is suitable to be represented by map and unordered_map exactly?

      EDIT: I see that the test case on which unordered map gave TLE involves hashing a multiple of some fixed number. Has this got something to do with the TLE(I believe it must be the reason). Also, was the testcase specifically designed so that unordered_map fails on it, How do we make sure not to fall in such traps if so?

»
8 years ago, # |
  Vote: I like it 0 Vote: I do not like it
  • »
    »
    8 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Thanks a lot! I'm sorry for posting on the wrong blog. I should have found that post myself. But I'm new here :D

  • »
    »
    8 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    This problem can resolved with reserve trick.

    Like this : 21771219.

    • »
      »
      »
      8 years ago, # ^ |
        Vote: I like it +8 Vote: I do not like it

      It's not a trick if you know how unordered_map works.

      • »
        »
        »
        »
        8 years ago, # ^ |
        Rev. 2   Vote: I like it -8 Vote: I do not like it

        I know how it works. Read post again carefully.

        I have explained in post how it works.

»
7 years ago, # |
  Vote: I like it +2 Vote: I do not like it

Hi. I have 3 questions.

  1. Why must the input value for the reserve() method be a power of 2?

  2. Is there formula to determine the input value for the reserve() method? Let's say we use mp.reserve(x) where x is a power of 2. Does that mean that x must be >= N, where N is the number of elements that we are going to input into the unordered_map?

  3. How did you determine the value for the max_load_factor? Is that supposed to be a magic value?

Please advise me.

»
7 years ago, # |
  Vote: I like it +18 Vote: I do not like it

Thank you for your hard work, but unfortunately I don't find it that much useful with all that painful implementation.

»
7 years ago, # |
  Vote: I like it 0 Vote: I do not like it

AC using map: 33860000

TLE using unordered_map: 33860168 [ Yes, I used the tricks you mentioned to make unordered map faster. Also I have used the Hash function that you gave in the post for pair<int, int>

So, how can I believe that unorded_map is faster than map ??

  • »
    »
    6 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    It's really confusing when to use unordered_map and when to use map. My submission with map takes 1450 ms: 38906751 Same submission with unordered_map takes 467 ms:38906911 Though both passed time limit (3 s), if the time limit was 1-1.3 s, solution using map would have failed.

  • »
    »
    6 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    You just need to use a better hash function for pair<int,int> to get your solution passed. The hash function proposed here for pair<int,int> clearly isn't a good one. Read the above discussion here. I have changed your hash function and your TLE code gets ac for that problem. Here's the ac code with modified hash function.

    • »
      »
      »
      6 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      But is it always safe to use unordered_map in contests, since in worst case unordered_map can be of O(n)? Should anyone use unordered_map always instead of map ?

»
6 years ago, # |
  Vote: I like it 0 Vote: I do not like it

SUMFOUR — 4 values whose sum is 0 on SPOJ in an example where unordered_map with reserve() will give AC and others will give TLE

»
6 years ago, # |
  Vote: I like it +3 Vote: I do not like it

ََُArpa youre the best :)

»
5 years ago, # |
  Vote: I like it 0 Vote: I do not like it

I have used unordered_map for this problem. Christmas Trees I got a TLE with this Then I changed the unordered_map to just map and got AC with this Can you explain what is happening here. [Sorry for my bad English]

»
5 years ago, # |
  Vote: I like it -6 Vote: I do not like it

Unordered_map : TLE submission link

map : AC submission link

i've no idea why...

  • »
    »
    5 years ago, # ^ |
      Vote: I like it +3 Vote: I do not like it

    It happens. Try to use the reserve method. You can implement Hash Map on your own too.

    • »
      »
      »
      5 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      Hi. Could you please explain how you arrive with values such as 1024 or 4096 while using reserve? Say, for example, I know that in my program there will be 10^7 insert operations. What value do I use then?

      Doesn't reserve(4096) mean set the appropriate number of buckets to hold at least 4096 elements in the hash table without exceeding the max_load_ratio?

»
5 years ago, # |
  Vote: I like it 0 Vote: I do not like it

Be careful with hash data structures. Even though std::unordered_map is often faster than std::map, it still has a worst case complexity of O(n) per operation and it is possible to design TLE hacks this way. See https://codeforces.me/blog/entry/62393.

»
4 years ago, # |
  Vote: I like it 0 Vote: I do not like it

What is unordred_map?

Shouldn't it be "What is unordered_map?"

»
4 years ago, # |
  Vote: I like it 0 Vote: I do not like it

what is the average time complexity of insertion/search in unordered_map if key is string ? Is it O(L) L=>length of string ?

»
4 years ago, # |
  Vote: I like it 0 Vote: I do not like it

Hi I have tried to change (unordered_)map to many thing like this ones but every time I get TLE on last testcase; I think this idea should be change but if anybody can help me, I ll be happy. link of submission

»
5 months ago, # |
  Vote: I like it 0 Vote: I do not like it

why my code MLE after using mp.reserve(1024)