I recently had need for a C++ collection which could be accessed with multiple keys. Specifically, I had an object that I wanted to look up either by name or id (in O(1) time). One way to do this is to create two unordered_maps – one keyed by id and another by name, but that is error-prone. I ended up finding a boost class which will do this. It took me a while get it working, so I thought I would share the result.
Last active
January 20, 2020 20:04
-
-
Save jlschrag/e49f0a09ff062226e5c3159c4dabf13a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//The object being stored in the collection | |
struct Person | |
{ | |
int Id; | |
std::string Name; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//A consumer of the collection | |
class PersonCache | |
{ | |
private: | |
PersonMap m_People; | |
public: | |
PersonCache(); | |
~PersonCache(); | |
bool IsEmpty() const | |
{ | |
return m_People.empty(); | |
} | |
Person* GetPersonByName(std::string lowerCaseName) const | |
{ | |
auto& indexByName = m_People.get<IndexByName>(); | |
auto iter = indexByName.find(lowerCaseName); | |
if (iter != indexByName.end()) return &iter.get_node()->value(); | |
return nullptr; | |
} | |
Person* GetPersonById(const int id) const | |
{ | |
auto& indexById = m_People.get<IndexById>(); | |
auto iter = indexById.find(id); | |
if (iter != indexById.end()) return &iter.get_node()->value(); | |
return nullptr; | |
} | |
PersonCache::iterator begin() | |
{ | |
return m_People.begin(); | |
} | |
PersonCache::iterator end() | |
{ | |
return m_People.end(); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//the collection... | |
//These structs just allow us to name the indexes | |
struct IndexById {}; | |
/// <summary> | |
/// This index uses a lower case key extractor, so any lookup against this index should submit a lower case string. | |
/// </summary> | |
struct IndexByName {}; | |
/// <summary> | |
/// When keys are pulled out for comparison, they will be converted to lower case. | |
/// Built from examples here: http://boost.2283326.n4.nabble.com/multi-index-index-based-on-case-insensitive-strings-td2565253.html | |
/// That link also provides a more generic, reusable way to do this which is more verbose. | |
/// </summary> | |
struct LowerCaseKeyExtractor | |
{ | |
typedef std::string result_type; | |
std::string operator()(const Person& person) const | |
{ | |
return boost::algorithm::to_lower_copy(person.Name); | |
} | |
}; | |
//This was implemented using ideas from https://stackoverflow.com/questions/39510143/how-to-use-create-boostmulti-index/39510606#39510606 | |
//There is a good explanation of each part there. See also https://www.boost.org/doc/libs/1_64_0/libs/multi_index/doc/tutorial/basics.html | |
typedef | |
boost::multi_index_container< | |
Person, //The data type stored | |
boost::multi_index::indexed_by< //List of indexes | |
boost::multi_index::hashed_unique< | |
boost::multi_index::tag<IndexById>, //Give the index a name | |
boost::multi_index::member<Person, int, &Person::Id> //Use Id as the key | |
>, | |
boost::multi_index::hashed_non_unique< | |
boost::multi_index::tag<IndexByName>, //Give the index a name | |
LowerCaseKeyExtractor | |
> | |
> | |
> PersonMap; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment