32

I have an std::map and I want to search for a key using a substring. For example, I have the following code:

#include <iostream>
#include <map>
#include <string>
using namespace std;

typedef std::map<std::string, std::string> TStrStrMap;
typedef std::pair<std::string, std::string> TStrStrPair;

int main(int argc, char *argv[])
{
    TStrStrMap tMap;

    tMap.insert(TStrStrPair("John", "AA"));
    tMap.insert(TStrStrPair("Mary", "BBB"));
    tMap.insert(TStrStrPair("Mother", "A"));
    tMap.insert(TStrStrPair("Marlon", "C"));

    return 0;
}

Now, I want to search for the position that holds the substring "Marl" and not "Marlon", if "Marla" is stored in the map. I want to find something that starts with "Marl". I need to find at most one position. Is this possible? If so, how?

I don't want to use any Boost libraries!

honk
  • 9,137
  • 11
  • 75
  • 83
cateof
  • 6,608
  • 25
  • 79
  • 153
  • Hold on, _"Marl and not Marlon"_ meaning you _don't_ want to find Marlon when searching for Marl ? – wilhelmtell Feb 19 '12 at 14:20
  • @wilhelmtell want to find something that starts with Marl. Maybe not Marlon, if Marla for example is stored in the map. I need to find at most one position. – cateof Feb 19 '12 at 14:26

5 Answers5

31

You can't efficiently search for substring, but you can for prefix:

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
using namespace std;

typedef map<string, string> TStrStrMap;
typedef pair<string, string> TStrStrPair;

TStrStrMap::const_iterator FindPrefix(const TStrStrMap& map, const string& search_for) {
    TStrStrMap::const_iterator i = map.lower_bound(search_for);
    if (i != map.end()) {
        const string& key = i->first;
        if (key.compare(0, search_for.size(), search_for) == 0) // Really a prefix?
            return i;
    }
    return map.end();
}

void Test(const TStrStrMap& map, const string& search_for) {
    cout << search_for;
    auto i = FindPrefix(map, search_for);
    if (i != map.end())
        cout << '\t' << i->first << ", " << i->second;
    cout << endl;
}

int main(int argc, char *argv[])
{
    TStrStrMap tMap;

    tMap.insert(TStrStrPair("John", "AA"));
    tMap.insert(TStrStrPair("Mary", "BBB"));
    tMap.insert(TStrStrPair("Mother", "A"));
    tMap.insert(TStrStrPair("Marlon", "C"));

    Test(tMap, "Marl");
    Test(tMap, "Mo");
    Test(tMap, "ther");
    Test(tMap, "Mad");
    Test(tMap, "Mom");
    Test(tMap, "Perr");
    Test(tMap, "Jo");

    return 0;
}

This prints:

Marl    Marlon, C
Mo      Mother, A
ther
Mad
Mom
Perr
Jo      John, AA
Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
9

When your substring is a prefix as in your example, you can use lower_bound to search for "Marl".

    map<string,string>::const_iterator m = tMap.lower_bound("Marl");
    cerr << (*m).second << endl;

This does not work for non-prefix substrings: in the general case, searching a map is not much different from searching other containers.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 2
    That answers the question for the example, "Marl" of "Marlon", but not the general question of searching for a substring. – wilhelmtell Feb 19 '12 at 14:11
  • @wilhelmtell Absolutely, you are correct. I updated the answer to mention that. Searching for prefix substring is the only case when searching a `map` is different from, say, searching a list. – Sergey Kalinichenko Feb 19 '12 at 14:17
  • 1
    Good start but that alone is not enough. You also need to check if the found string is actually prefixed with the search string (it could just be lexicographically greater but with different prefix). – Branko Dimitrijevic Feb 19 '12 at 14:43
  • @BrankoDimitrijevic Of course this is not a complete solution, only a skeleton, a hint at how a real solution *can be* implemented. Although absolutely necessary, the additional logic hides the "beef" of the solution. That is why I skipped the other necessary check to see if the returned value is at `tMap.end()`. – Sergey Kalinichenko Feb 19 '12 at 15:25
5

I'd like to expand on the answer by Sergey by providing a full solution using map::lower_bound(). As mentioned in the comments on that answer, you have to check whether lower_bound() returns tMap.end(). If not, then you also have to check whether the found key is actually prefixed with the search string. Latter can be checked, for example, by using string::compare(). As a result, my C++11 solution looks as follows:

std::map<std::string, std::string> myMap{
    {"John", "AA"}, {"Mary", "BBB"}, {"Mother", "A"}, {"Marlon", "C"}, {"Marla", "D"}
};
std::string prefix("Marl");

auto it = myMap.lower_bound(prefix);
if (it != std::end(myMap) && it->first.compare(0, prefix.size(), prefix) == 0)
    std::cout << it->first << ": " << it->second << std::endl;

Output:

Marla: D

However, if you want to find all keys in your map that are prefixed with the search string, then you can use the following loop:

for (auto it = myMap.lower_bound(prefix); it != std::end(myMap) && it->first.compare(0, prefix.size(), prefix) == 0; ++it)
    std::cout << it->first << ": " << it->second << std::endl;

Output:

Marla: D
Marlon: C

Code on Ideone

honk
  • 9,137
  • 11
  • 75
  • 83
3

To search for a substring of a key in a map you have no choice but to either use a new map on a special kind of key type or to search your map in O(n). std::map uses (by default) operator<() for ordering keys and for searching, and that compare function for std::string is a plain lexicographical compare.

If you create a new map on a special key type that has operator<() compare on basis of a substring take note that this will also affect the decision of whether a new element to insert would be a duplicate. In other words, such a map will only have elements that are not substrings of each other.

The O(n) search practically means you use std::find() over the map, with a custom predicate that takes a std::pair<std::string,std::string> and returns true if the second element of the pair is a substring of the first.

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
0
typedef TStrStrMap::value_type map_value_type;

struct key_contains_substring
   : std::binary_function<map_value_type, std::string, bool>
{
    bool operator()(const map_value_type& map_value, const std::string& substr)
    {
         return std::search(map_value.first.begin(), map_value.first.end(),
                    substr.begin(), substr.end()) != map_value.first.end();  
    }
};

...


TStrStrMap::const_iterator it = std::find_if(tMap.begin(), tMap.end(), 
        std::bind2nd(key_contains_substring(), "Marl");
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • Does not compile. `no matching function for call to 'search(std::__cxx11::basic_string::const_iterator, std::__cxx11::basic_string::const_iterator, std::__cxx11::basic_string::const_iterator, bool)'` – xamid Aug 30 '20 at 15:23
  • @xamid. It was misplaced parenthesis. Fixed – Armen Tsirunyan Aug 30 '20 at 15:48
  • The first part compiled that way. There still is some closing parenthesis missing after `"Marl"`. Then, in order to compile the second part, I also had to make operator() constant, i.e. `bool operator()(...) const {`. – xamid Aug 30 '20 at 16:44