Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » stl::map lookup with three keys?

This thread is locked; no one can reply to it. rss feed Print
stl::map lookup with three keys?
Mark Oates
Member #1,146
March 2001
avatar

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?

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

X-G
Member #856
December 2000
avatar

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.

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

Thomas Fjellstrom
Member #476
June 2000
avatar

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

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Marco Radaelli
Member #3,028
December 2002
avatar

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
Member #856
December 2000
avatar

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.)

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

Mark Oates
Member #1,146
March 2001
avatar

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.

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

X-G
Member #856
December 2000
avatar

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.

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

Kitty Cat
Member #2,815
October 2002
avatar

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?

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Mark Oates
Member #1,146
March 2001
avatar

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?

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Dario ff
Member #10,065
August 2008
avatar

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.

TranslatorHack 2010, a human translation chain in a.cc.
My games: [GiftCraft] - [Blocky Rhythm[SH2011]] - [Elven Revolution] - [Dune Smasher!]

gnolam
Member #2,030
March 2002
avatar

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.

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

Bob
Free Market Evangelist
September 2000
avatar

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.

--
- Bob
[ -- All my signature links are 404 -- ]

Matthew Leverton
Supreme Loser
January 1999
avatar

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
Member #1,146
March 2001
avatar

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! :)

--
Visit CLUBCATT.com for cat shirts, cat mugs, puzzles, art and more <-- coupon code ALLEGRO4LIFE at checkout and get $3 off any order of 3 or more items!

AllegroFlareAllegroFlare DocsAllegroFlare GitHub

Matthew Leverton
Supreme Loser
January 1999
avatar

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
Member #856
December 2000
avatar

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

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

Thomas Fjellstrom
Member #476
June 2000
avatar

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.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Tobias Dammers
Member #2,604
August 2002
avatar

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).

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Go to: