stl::map lookup with three keys?
Mark Oates

I want to create something using a stl::map, except I want it to give me the value when I provide it with three keys.

Specifically, the idea is to create a collection of bitmaps that are pre-rendered strings of text, and I want the stl::map to return the bitmap, given 3 search factors:
1. the font name (as a string)
2. the font size (as a signed int)
3. the string of text that has been rendered

something like map<BITMAP*, string_key, int_key, string_key> text_render_bin.

If this isn't possible, I would probably do something like create a struct with two of the keys and the BITMAP*, put the third key in a map, then find() the key in the container, and just loop through all them until the right one is found. But that doesn't seem like the best way to do it.

I'm wondering, is a more efficient way to do it using a map or something else? Is it possible to create a 3-key map, or an n-key map?

X-G

1) Create structure containing members for font name, size and string
2) Add comparison operators and a hash function to it
3) std::hash_map<infostruct, BITMAP*>

Also you've consistently put the BITMAP* in with the keys twice in your post, when it's actually the value you want to look up. Make sure you understand how maps work.

Thomas Fjellstrom

You can also hack it up by using a string representation of the font properties. Though a proper wraper class is probably better.

Marco Radaelli

I second what Thomas said (if I got it right), putting all your data (font name, font size, text) in a string and leaving the hashing to stl::map will be easier.

That if you are going to handle a bunch of strings, if we're talking about several hundreds of them maybe an ad hoc structure will probably be more efficient.

X-G

leaving the hashing to stl::map will be easier.

std::map is not a hashing container.

(It's generally implemented as a red-black tree, but implementation details aside it can't be implemented as a hash map anyway because of the ordering constraint.)

Mark Oates
X-G said:

1) Create structure containing members for font name, size and string
2) Add comparison operators and a hash function to it
3) std::hash_map<infostruct, BITMAP*> [www.sgi.com]

a comparison operator for two structs? would an operand== suffice?

struct text_render_info
{
   const char *font_filename;
   const char *text;
   int font_size;
};
bool operator==(const &text_render_info, const &text_render_info) const
{
    return (strcmp(info1.text, info2.text) == 0
     && strcmp(info1.font_filename, info2.font_filename) == 0
     && info1.font_size == info2.font_size);
}

How do I create the hash function? In the example it looks like all I have to do is pass hash<text_render_info> in my declaration.

X-G

a comparison operator for two structs? would an operand== suffice?

"Operand" means one of the arguments to an operator. Yes, the equality operator is all you need.

Quote:

How do I create the hash function?

Like it says on the documentation page, one of the template arguments to the hash map is a hash function. Write one, pass it.

Kitty Cat
X-G said:

"Operand" means one of the arguments to an operator. Yes, the equality operator is all you need.

I thought it was the less-than (<) operator you needed for std::map, since the key lookup uses a binary search?

Mark Oates
X-G said:

hash function. Write one

I don't know how to do this. A hash function converts the data struct into an index? How is this any different from Thomas' suggestion putting everything into a single string index?

Dario ff

I've coded this last year when I found out C++ lacked Hash structures.

#SelectExpand
1class HashDataString { 2 public: 3 std::string id; 4 void *value; 5 HashDataString(std::string i, void *v) { 6 id = i; 7 value = v; 8 } 9 bool Compare(std::string d) { 10 if (id == d) return true; 11 return false; 12 } 13 void *GetValue() {return value;} 14 std::string GetIdentifier() {return id;} 15 ~HashDataString() { 16 id.clear(); 17 } 18}; 19 20 21class HashString { 22 public: 23 std::vector<HashDataString> list; 24 HashString() { 25 list.clear(); 26 } 27 void push_back(std::string i, void *v) { 28 list.push_back(HashDataString(i, v)); 29 } 30 void *get(std::string i) { 31 std::vector<HashDataString>::iterator it; 32 for (it=list.begin(); it!=list.end(); it+=1) { 33 if ((*it).Compare(i)) return (*it).GetValue(); 34 } 35 return NULL; 36 } 37 HashDataString *get_data(int i) { 38 return &(list[i]); 39 } 40 void clear() { 41 list.clear(); 42 } 43 ~HashString() { 44 list.clear(); 45 } 46 int size() { 47 return list.size(); 48 } 49};

There you have a base. But it can only store (void *) so you'll have to cast your pointers :P

EDIT: Sorry for giving no explanation.
Basically, you use HashString class and push_back() elements with a string identifier and void *. The you use the get function with the string identifier.

gnolam
Kitty Cat said:

I thought it was the less-than (<) operator you needed for std::map, since the key lookup uses a binary search?

For std::map, yes. But not std::hash_map, which X-G was referring to.

Bob

You can use boost::tuple to create the 3-item container:

   std::map<boost::tuple<BITMAP*, std::string, int>, std::string> table;

   table[boost::make_tuple(my_bitmap, "Courrier New", 5)] = "some string";

Hope this helps.

Matthew Leverton

When using hash_map:

Quote:

/usr/include/c++/4.4/backward/backward_warning.h:28:2: warning: #warning This file includes at least one deprecated or antiquated header which may be removed without further notice at a future date. Please use a non-deprecated interface with equivalent functionality instead. For a listing of replacement headers and interfaces, consult the file backward_warning.h. To disable this warning use -Wno-deprecated.

When using unordered_map:

Quote:

/usr/include/c++/4.4/c++0x_warning.h:31:2: error: #error This file requires compiler and library support for the upcoming ISO C++ standard, C++0x. This support is currently experimental, and must be enabled with the -std=c++0x or -std=gnu++0x compiler options.

C++ is a beautiful language.

Mark Oates
Bob said:

You can use boost::tuple to create the 3-item container:

I like this, but it doesn't like the tuple argument when assigning it to a map.

#include "boost/tuple/tuple.hpp"
#include "boost/tuple/tuple_comparison.hpp"

class text_render_bin
{
public:
  map<boost::tuple<string, int, string>, ALLEGRO_BITMAP*> table;
  table[boost::make_tuple("string", 12, "my_bitmap")] = al_get_target_bitmap(); //<-- errors here
};

errors:

1>------ Build started: Project: game framework 01, Configuration: Debug Win32 ------
1>  main.cpp
1>c:\users\mark\documents\visual studio 2010\projects\game framework 01\game framework 01\text_render_manager.h(84): error C2057: expected constant expression
1>c:\users\mark\documents\visual studio 2010\projects\game framework 01\game framework 01\text_render_manager.h(84): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>c:\users\mark\documents\visual studio 2010\projects\game framework 01\game framework 01\text_render_manager.h(84): error C2864: 'text_render_bin::table' : only static const integral data members can be initialized within a class
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

[edit] I'm retarded. :-[

ok, it looks like that works so far! :)

Matthew Leverton

Switching from a map to an unordered_map took my top secret C++ program down from processing 1.1M records in 2+ minutes to 44 seconds. >:(

Not bad considering the same program took PHP over five hours. ;D

I should probably take a look at boost one of these days. Looks like it makes for even better job security.

X-G

How is this any different from Thomas' suggestion putting everything into a single string index?

1. The hash is a 32/64-bit integer as opposed to a string and is thus much faster to deal with than a string.
2. Thomas suggested using a map, which does a binary search, which is thus slower than a hash table lookup. You can do it if you like, but in that case I'd recommand having the struct and implementing the < operator instead.

When using hash_map:

Never had that problem. But I try to use stlport whenever possible.

Bob said:

You can use boost::tuple to create the 3-item container:

Is he ready for the awesome magic of boost? Can he contain the power? ;)

Quote:

table[boost::make_tuple("string", 12, "my_bitmap")] = al_get_target_bitmap(); //<-- errors here

... that's in the middle of the declaration. Statements go in functions. Forgotten your elementary C++? :P

Thomas Fjellstrom
X-G said:

2. Thomas suggested using a map, which does a binary search, which is thus slower than a hash table lookup. You can do it if you like, but in that case I'd recommand having the struct and implementing the < operator instead.

Only because thats what the code was using.

In this case, I would probably use a hash_map, or unordered_map (which is what its called now?), since it lends itself very well to the kind of data you're trying to store. That and you really don't need the ordering constraint, all it does is slow you down.

Tobias Dammers

I don't know how to do this. A hash function converts the data struct into an index? How is this any different from Thomas' suggestion putting everything into a single string index?

A hash map looks up the integer hash first, which is very fast: An integer comparison typically executes in a single clock tick, and requires no branching except the final decision (greater / less / equal). A string comparison, OTOH, requires a loop that compares on each iteration, and typically finishes at relatively unpredictable indices. So instead of doing a binary search on the string keys (or worse), you do a binary search on the faster integer keys. Once you have found your match by hash, you may have to search through a few entries if your hash collides, but with a good hash function, the chances for such a collision are relatively low (with the perfect hash function, the chances for any two hashes to collide would be 1 : 232 for 32-bit hashes).
The challenge is to write a hash function that balances hashing speed (the time required to create a hash) against entropy (the probability of a collision).

Thread #602556. Printed from Allegro.cc