All about Hash Map

06 Dec 2020 LeetCode and Note

Use hash table to avoid the O(n) time required for searching the elements.

A simple example in 136. Single Number:

from collections import defaultdict
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        hash_table = defaultdict(int)
        for i in nums:
            hash_table[i] += 1
        
        for i in hash_table:
            if hash_table[i] == 1:
                return i

When handling hashmap(dict actually) for counting, we can from collections import defaultdict and use defaultdict(int). Cuz int’s factory function has default value 0. Using defaultdict here is actually defining a dict with default value 0s.

An example of defaultdict(int):

    str = 'mississippi'
    dic = defaultdict(int)
    for i in str:
         dic[i]+= 1
    list(dic.items())

Output

[('i',4), ('p',2), ('s',4), ('m',1)]

In the same question, my solution with C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> hashmap;
        for (auto &it: nums)
            hashmap.find(it) == hashmap.end() ? hashmap[it] = 1 : hashmap[it] += 1;
            //cout << "it" << it << "value" << hashmap[it] <<endl;
        for (auto &it: hashmap)
            //cout <<"it->first" << it.first << "it->second" << it.second << endl;
            if (it.second == 1)
                return it.first;
        return -1;
    }
};

We can simply replace line 6 with hashmap[it]++; cuz unordered_map is also initialized with default 0.

It is actually safe to assume that the values inside Foo are always initialized to zero because of the behaviour of operator[] When the default allocator is used, this results in the key being copy/move constructed from key and the mapped value being value-initialized. You do not provide a constructor which means that each field in Foo will be value initialized individually which for primitive types means zero initialization.

But the problem you are actually facing here is that a field called “Apple” does not exist in your map. Unfortunately the semantics of operator[] are such that if the value does not exist, it will be created on the fly. You probably didn’t even want to access a non-existent field in the map and you are asking whether it is always initialized to zero so that you can use this fact to check whether the element was there. For this purpose however, you should either use the find() or at() member function. find() will return an iterator pointing to the end of the map if the element does not exist. That means you could guards the element access using

if (auto apple = map.find("Apple"); apple != map.end()) {
    std::cout << apple->second.num << '\n';
    std::cout << apple->second.state << '\n';
}

(with the C++17 if statement initializer)

at() will throw an exception if the element is not found.

    std::cout << map.at("Apple").num << '\n';
    std::cout << map.at("Apple").state << '\n';

This will crash your program with a std::out_of_range exception. You might feel temped to catch this exception to check whether the element existed. Don’t do this. It is very bad practice to use exceptions for control flow. On top of that exception are dead slow when they are being thrown.

Reference

[1] Default value of static std::unordered_map - https://stackoverflow.com/questions/51667662/default-value-of-static-stdunordered-map

Comments