8

I have some (working) code that uses a multimap<string,string>. I'd like to change it to disallow duplicate values on the same key (obviously different values on the same key are fine, otherwise I wouldn't use a multimap).

Surprisingly the type doesn't seem to have a built-in way to avoid duplicates nor to find a key-value pair (only to find a key). But I figure someone on SO must have a ready-made workaround. Anyone?

Qwertie
  • 16,354
  • 20
  • 105
  • 148
  • Are duplicate values allowed that have different keys? How many keys would you expect to be used multiple times, and how often at that? – ypnos Sep 07 '12 at 20:30
  • Sure, the same value on different keys is fine. In my case the fan-out is small (usually 1 or 2 values per key) so DeadMG's suggestion to switch to `std::map>` is not efficient or convenient (since multimap-based code is written already). – Qwertie Sep 07 '12 at 20:49

4 Answers4

5

Here's what I came up with:

template<class K, class V>
typename multimap<K, V>::const_iterator find_pair(const multimap<K, V>& map, const pair<K, V>& pair)
{
    typedef multimap<K, V>::const_iterator it;
    std::pair<it,it> range = map.equal_range(pair.first);
    for (it p = range.first; p != range.second; ++p)
        if (p->second == pair.second)
            return p;
    return map.end();
}
template<class K, class V>
bool insert_if_not_present(multimap<K, V>& map, const pair<K, V>& pair)
{
    if (find_pair(map, pair) == map.end()) {
        map.insert(pair);
        return true;
    }
    return false;
}

(This is not efficient when there are a large numbers of values attached to a single key, but in my case there are very few values on each key.)

Qwertie
  • 16,354
  • 20
  • 105
  • 148
  • 1
    If the only thing you use the returned iterator for is comparison, then why not simply return `bool` instead? – jrok Sep 07 '12 at 21:20
  • Because who knows, I might want to `map.erase(find_pair(...))` someday. A contains_pair() wrapper might be handy too, admittedly. – Qwertie Sep 07 '12 at 21:39
4

It seems that

      std::set<std::pair<std::string,std::string>>>

will have exactly the properties you are looking for.

However it is not a map nor multimap. You can keep both multimap and set of key,value pairs or create this set for checking consistency only.

I would use this set and create an adapter over it to multimap interface. Maybe it is not the easiest solution to implement, but with best performance efficiency.

See "adapter design pattern" questions for references.


[UPDATE]

See my working example as a start point.

E.g. how to iterate over all values for the key - see:

typedef  std::set<std::pair<std::string, std::string> > ssset;

ssset::iterator get_key(ssset& s, std::string key)
{
  ssset::iterator it = s.lower_bound(std::make_pair(key, ""));
  if (it != s.end() && it->first == key) return it;
  return s.end();   
}

for (ssset::iterator it = get_key(s, "abc"); it != s.end() && it->first == "abc"; ++it)
   std::cout << it->first << "->" << it->second << std::endl;
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • The problem is that now I cannot ask for the value(s) associated with a key, as a pair is required for search. – Qwertie Sep 07 '12 at 22:28
  • You can search for lower bound of (key,""). If found with the key it is the first value. Iterate till the other key begins. This could enclosed in adapter i mentioned – PiotrNycz Sep 08 '12 at 01:00
4

std::map<std::string, std::set<std::string>> would appear to have exactamondo the properties you are looking for (although inferior complexity to unordered_map and unordered_set).

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • He is using a multimap because he has multiple keys. – Rapptz Sep 07 '12 at 20:43
  • map and multimap allow "a","a" too. DeadMG's suggestion is workable but not ideal for my situation (see comment above... especially since I'm on C++03 => more worries about copy constructors.) – Qwertie Sep 07 '12 at 20:54
  • I misundertood the question. Ignore my comment. – PiotrNycz Sep 07 '12 at 20:55
1

My suggestion to you would be to wrap your multimap in a class and simply do the verification in the method where you add something into the map. The rest of the functions would simply pass through to the multimap's methods. It makes a lot of boiler plate code but if you ever need to do other types of verifications it'll be easier this way.

Borgleader
  • 15,826
  • 5
  • 46
  • 62