Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant way to find keys with given prefix in std::map or elements in std::set

I have map, which keys are std::string. I want to find those elements in the map which starts with "DUPA/" prefix. Finding lower bound is easy but upper bound is a bit problematic. I wrote such piece of code:

const char* prefix = "DUPA/";
const char* firstAfterPrefix = "DUPA0";
auto prefixedBeginIt = myMap.upper_bound(prefix);
auto prefixedEndIt = myMap.lower_bound(firstAfterPrefix);

The code works fine but I don't consider it to be elegant, as one has to know that 0 is first next to / in ASCII table. Second way would be to copy prefix and increment last sign. Do you know more elegant solution?

like image 553
Mariusz Jaskółka Avatar asked Jun 23 '17 09:06

Mariusz Jaskółka


People also ask

How do you check if an element is a key in a map C++?

To check for the existence of a particular key in the map, the standard solution is to use the public member function find() of the ordered or the unordered map container, which returns an iterator to the key-value pair if the specified key is found, or iterator to the end of the container if the specified key is not ...

How do you check if an std::map contains a key?

Check if map contains a key using std::map::countsize_type count (const key_type& K) const; It finds & returns the count of number of elements in map with key K. As map contains elements with unique key only. So, it will return 1 if key exists else 0.

How do you find the prefix of a string in C++?

The match_results::prefix() is an inbuilt function in C++ which is used to get the string which is preceding the matched string in the input target string.

How do I get map elements in C++?

C++ map find() function. C++ map find() function is used to find an element with the given key value k. If it finds the element then it returns an iterator pointing to the element. Otherwise, it returns an iterator pointing to the end of the map, i.e., map::end().


2 Answers

If you can assume that CHAR_MAX would not be a valid character in your strings, then you can create firstAfterPrefix by appending CHAR_MAX (or the maximum value of your character type if it's not char).

std::string prefix = "DUPA/";

constexpr auto char_max = std::numeric_limits<decltype(prefix)::value_type>::max();
std::string firstAfterPrefix = prefix + char_max;

auto prefixedBeginIt = myMap.lower_bound(prefix);
auto prefixedEndIt = myMap.lower_bound(firstAfterPrefix);

Note the use of lower_bound for both bounds. Like Gill I am using std::string to simplify the exposition.


If you can use C++14 and specify the Compare template argument of the container then another way is to use a custom probe object:

struct PrefixProbe { std::string_view prefix; };
bool operator<(PrefixProbe a, std::string_view b) { return a.prefix < b.substr(0, a.prefix.size()); }
bool operator<(std::string_view a, PrefixProbe b) { return a.substr(0, b.prefix.size()) < b.prefix; }

std::map<std::string, myValue, std::less<>> myMap;
//                             ^~~~~~~~~~~
//                             where the magic happens

auto prefixBegin = myMap.lower_bound(PrefixProbe { prefix });
auto prefixEnd = myMap.upper_bound(PrefixProbe { prefix });

std::string_view is C++17 but is not required to make this work.

equal_range would reduce the last two lines to a single line:

auto [ prefixBegin, prefixEnd ] = myMap.equal_range(PrefixProbe { prefix });

If you are prepared to use the STL algorithms instead of the container member functions then this can be done without altering the container type, but would be less efficient:

auto prefixBegin = std::lower_bound(cbegin(myMap), cend(myMap), PrefixProbe { prefix }, std::less<>{});
auto prefixEnd = std::upper_bound(cbegin(myMap), cend(myMap), PrefixProbe { prefix }, std::less<>{});

auto [ prefixBegin, prefixEnd ] = std::equal_range(cbegin(myMap), cend(myMap), PrefixProbe { prefix }, std::less<>{});
like image 91
Oktalist Avatar answered Oct 24 '22 01:10

Oktalist


I think the solution you mentioned is already the most elegant. The KISS way loses a lot of performance, that is, checking the key each time:

while(prefixedBeginIt->first == prefix)
{
 //...
 ++prefixedBeginIt;
}

Thus I think calculating the next char is the best approach:

std::string firstAfterPrefix = prefix;
++firstAfterPrefix[firstAfterPrefix.length() - 1];
auto prefixedEndIt = myMap.lower_bound(firstAfterPrefix);
like image 5
Hatted Rooster Avatar answered Oct 23 '22 23:10

Hatted Rooster