Search code examples
c++dictionaryhashmap

Pair unordered_map STL search for both


I want to create a hashmap that I can search for both part of the key somehow. Say I have a pair and I want this to be the key. I wouldn't like to search for the exact {string, id} but sometime to search for the int and sometime for the string, is it possible to do this?


Solution

  • Yes it's possible ! However it's not that useful and has a bad design .

    The solution is to use a custom compare type which will customize the comparing operation . If you want one behavior from the comparer then you only need a free context compare type which does whatever you want .

    But if you want to alter the behavior from outside the map as needed you will use a comparer with a context which outlives the map

    struct CompareContext
    {
       bool id = false;
    };
    
    struct Comparer
    {
       const CompareContext& ctx;
    
       using arg_t = std::pair<std::string, int>;
    
       Comparer(const CompareContext& ctx) : ctx{ ctx } {}
    
       bool operator()(const arg_t& first, const arg_t& second) const
       {
           if (ctx.id)
           {
               std::less<int> l;
               return l(first.second, second.second);
           }
           else
           {
               std::less<std::string> l;
               return l(first.first, second.first);
           }
       }
    
    };
    
    
    int main()
    {
        CompareContext ctx;
        Comparer comp{ ctx };
        using key_t = Comparer::arg_t;
        using val_t = int;
        using map_t = std::map<key_t, val_t, Comparer>;
    
        map_t mp{ comp };
    
    }
    

    Another example to search using the string or int of key std::pair<std::string, int>

    #include <map>
    #include <iostream>
    
    int main()
    {
       using key_t = std::pair<std::string, int>;
       auto cmp = [](const key_t & first, const key_t& second)
       {
          std::less<int> li;
          if (li(first.second, second.second))
             return true;
          std::less<std::string> ls;
          return ls(first.first, second.first); 
       };
    
       using map_t = std::map<key_t, int, decltype(cmp)>;
    
       key_t key1{ "string1", 1 };
       key_t key2{ "string2", 2 };
    
       map_t mp{ cmp };
       mp.emplace(key1, 1);
       mp.emplace(key2, 2);
    
       key_t search_1{ "string1", 5 }; // match using string
       key_t search_2{ "string5", 2 }; // match using int
       key_t search_3{ "string3", 3 }; // doesn't exist
    
       auto it = mp.find(search_1); // search and if not exist returns an iterator to end
       if (it != mp.end())
          std::cout << "Found " << it->second << std::endl;
    
       auto val = mp[search_2];
       std::cout << "Found " << val << std::endl;
    
       val = mp[search_3]; // since not found a node will be created with key search_3
       std::cout << "Created a node with int = " << val << std::endl;
    
    }
    

    Note that std::map doesn't search using equality lhs == rhs but compares with less than and greater than since it is sorted and this approach will be faster

    On the other hand std::unordered_map uses a hasher (usually std::hash) to test for equality since it isn't ordered .