Is it possible to overload operator== for const char* so that the actual C string and not the pointer values would be used to determine if the map elements are equal
No it's not, and yes, it's not a good idea for exactly the reason pointed out in the question and because you don't need char*, you can use a std::string instead. (you can provide a custom compare function - as pointed out by simonc, but I'd advise against it)
//Could these be referring to different map elements?
int i = imap["hello"];
int j = imap["hello"];
Yes, and they can even refer to elements that don't exist yet, but they'll be created by operator[] and be value initialized. The same issue exists with assignment:
imap["hello"] = 0;
imap["hello"] = 1;
The map could now have 1 or 2 elements.
Answer from Luchian Grigore on Stack OverflowMake std::map find a std::string key using a char* without converting it to a std::string.
Map to String - C++ Forum
performance - how to best achieve string to number mapping in a c program - Stack Overflow
C Map String to Function - Stack Overflow
Is it possible to overload operator== for const char* so that the actual C string and not the pointer values would be used to determine if the map elements are equal
No it's not, and yes, it's not a good idea for exactly the reason pointed out in the question and because you don't need char*, you can use a std::string instead. (you can provide a custom compare function - as pointed out by simonc, but I'd advise against it)
//Could these be referring to different map elements?
int i = imap["hello"];
int j = imap["hello"];
Yes, and they can even refer to elements that don't exist yet, but they'll be created by operator[] and be value initialized. The same issue exists with assignment:
imap["hello"] = 0;
imap["hello"] = 1;
The map could now have 1 or 2 elements.
You can provide a map with a custom comparitor which compares the C strings
std::map<const char*,YourType,CstrCmp>;
bool CstrCmp::operator()(const char* a, const char* b) const
{
return strcmp(a, b) < 0;
}
I have a map that has std::string's as its key type, and i'll need to find elements in this map using strings that are passed to me as char* . Normally find() would automatically convert my char* string to an std::string before searching, but is there a way to make it compare the std::string keys to char* strings using std::string::c_str() and strcmp() ? (i can't change the data types here, i really need to use std::stringfor the key.)
Sort your table, and use the standard library function bsearch to perform a binary search.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct entry {
char *str;
int n;
};
/* sorted according to str */
struct entry dict[] = {
"dont_live", 4,
"half_kill", 3,
"kill", 1,
"live", 2,
};
int compare(const void *s1, const void *s2)
{
const struct entry *e1 = s1;
const struct entry *e2 = s2;
return strcmp(e1->str, e2->str);
}
int
main (int argc, char *argv[])
{
struct entry *result, key = {argv[1]};
result = bsearch(&key, dict, sizeof(dict)/sizeof(dict[0]),
sizeof dict[0], compare);
if (result)
printf("%d\n", result->n);
return 0;
}
Here's what you get when you run the program.
$ ./a.out kill
1
$ ./a.out half_kill
3
$ ./a.out foo
<no output>
PS: I reused portions of sidyll's program. My answer should now be CC BY-SA compliant :p
A possible solution:
#include <stdio.h>
#include <string.h>
struct entry {
char *str;
int n;
};
struct entry dict[] = {
"kill", 1,
"live", 2,
"half_kill", 3,
"dont_live", 4,
0,0
};
int
number_for_key(char *key)
{
int i = 0;
char *name = dict[i].str;
while (name) {
if (strcmp(name, key) == 0)
return dict[i].n;
name = dict[++i].str;
}
return 0;
}
int
main (int argc, char *argv[])
{
printf("enter your keyword: ");
char s[100]; scanf("%s", s);
printf("the number is: %d\n", number_for_key(s));
return 0;
}
I was thinking of making two arrays, one of strings, and another with their corresponding function pointers.
That is the right approach. Make wrapper functions that take arguments of identical type* then call the "real" functions from inside your wrappers.
If you would like to "earn additional points for style", sort the array of strings alphabetically, and use binary search on the array of strings. This will let you save a few CPU cycles when the two arrays get bigger.
* Perhaps you could use char **, because that's what your exec_if_built_in takes.
If your cases are so varied that a table-driven approach is inappropriate, then maybe an if/else-if cascade is the best solution.
if(!strcmp(args[0], "cat")) {
/* cat a file */
} else if(!strcmp(args[0], "dog")) {
/* dog a file */
} else if(!strcmp(args[0], "echo")) {
/* create an echo chamber */
} ...
You cannot avoid this. There are more possible strings than integers, therefore hash collisions are imminent. Read up on hashmaps - it's a data structure that explicitly takes collisions into account and works around them.
A map data structure and "collision" cannot be separated. the way you started your implementation seems fine, here's how you should handle collisions :
Adding a new entry into the map
- calculate
hashcodeforkey - compute
indexfrom hashcode (more or lessindex=hashcode value%size of keyset) - if
keyset[index]is not null- if keyset[index] != key (ie. for strings, use strcmp) increment
indexmodulussize of keyset, then goto 3
- if keyset[index] != key (ie. for strings, use strcmp) increment
- put
valueintoentryset[index]
Getting a value from the map
- calculate
hashcodeforkey - compute
indexfrom hashcode (more or lessindex=hashcode value%size of keyset) - if
keyset[index]is not null- if keyset[index] != key (ie. for strings, use strcmp) increment
indexmodulussize of keyset, then goto 3
- if keyset[index] != key (ie. for strings, use strcmp) increment
- if
keyset[index]is null return null - return
entryset[index]
Deleting an entry from the map
- calculate
hashcodeforkey - compute
indexfrom hashcode (more or lessindex=hashcode value%size of keyset) - if
keyset[index]is not null- if keyset[index] != key (ie. for strings, use strcmp) increment
indexmodulussize of keyset, then goto 3
- if keyset[index] != key (ie. for strings, use strcmp) increment
- set
keyset[index]andentryset[index]to null
As you can see, you can put step 1 to 3 into a function int findIndexFromKey(Map *map, char *key); and most of the work is done
** EDIT **
Of course, you also have to check if your map is not full before (or while) adding a new entry, otherwise you'll just loop undefinitely.
map<char*,int> mapp;
They key type here is not "c string". At least not, if we define c string to be "an array of characters, with null terminator". The key type, which is char*, is a pointer to a character object. The distinction is important. You aren't storing strings in the map. You are storing pointers, and the strings live elsewhere.
Unless you use a custom comparison function object, std::map uses operator<(const key_type&,key_type&) by default. Two pointers are equal if, and only if they point to the same object.
Here is an example of three objects:
char A[] = "apple";
char B[] = "apple";
const char (&C)[6] = "apple"
First two are arrays, the third is an lvalue reference that is bound to a string literal object that is also an array. Being separate objects, their address is of course also different. So, if you were to write:
mapp[A] = 10;
std::cout << mapp[B];
std::cout << mapp[C];
The output would be 0 for each, because you hadn't initialized mapp[B] nor mapp[C], so they will be value initialized by operator[]. The key values are different, even though each array contains the same characters.
Solution: Don't use operator< to compare pointers to c strings. Use std::strcmp instead. With std::map, this means using a custom comparison object. However, you aren't done with caveats yet. You must still make sure that the strings must stay in memory as long as they are pointed to by the keys in the map. For example, this would be a mistake:
char A[] = "apple";
mapp[A] = 10;
return mapp; // oops, we returned mapp outside of the scope
// but it contains a pointer to the string that
// is no longer valid outside of this scope
Solution: Take care of scope, or just use std::string.
It can be done but you need a smarter version of string:
struct CString {
CString(const char *str) {
strcpy(string, str);
}
CString(const CString ©); // Copy constructor will be needed.
char string[50]; // Or char * if you want to go that way, but you will need
// to be careful about memory so you can already see hardships ahead.
bool operator<(const CString &rhs) {
return strcmp(string, rhs.string) < 0;
}
}
map<CString,int> mapp;
mapp["someString"] = 5;
But as you can likely see, this is a huge hassle. There are probably some things that i have missed or overlooked as well.
You could also use a comparison function:
struct cmpStr{
bool operator()(const char *a, const char *b) const {
return strcmp(a, b) < 0;
}
};
map<char *,int> mapp;
char A[5] = "A";
mapp[A] = 5;
But there is a lot of external memory management, what happens if As memory goes but the map remains, UB. This is still a nightmare.
Just use a std::string.
The problem is that you cannot use C-style arrays in standard library containers, because they do not meet certain requirements such as being assignable and copy constructible. However, you can use the std::array container if you know the size of the array at compile time, or an std::vector if not:
#include <map>
#include <array>
#include <vector>
std::map<std::string, std::array<std::string, 4>> m_fixed;
std::map<std::string, std::vector<std::string>> m_dynamic;
Note that std::array is part of C++11. If you don't have C++11 support, you may use the TR1 version std::tr1::array from the <tr1/array> header, or the Boost library version.
In C++ what you're trying to accomplish can be done with a std::map<std::string, std::vector<std::string> >.
Alternatively, since you're really just associating multiple string values with a single string key, you could use std::multimap<std::string, std::string>.
Also note that if you don't care about being able to iterate through keys in order, std::unordered_multimap<std::string, std::string> may be preferable (you normally expect constant complexity instead of logarithmic).
I recommend std::map<>
#include <map>
#include <string>
std::map<int, std::string> mapping;
// Initialize the map
mapping.insert(std::make_pair(1, "Request System Info"));
mapping.insert(std::make_pair(2, "Change System Info"));
mapping.insert(std::make_pair(10, "Unkown Error"));
// Use the map
std::map<int, std::string>::const_iterator iter =
mapping.find(num);
if (iter != mapping.end())
{
// iter->second contains your string
// iter->first contains the number you just looked up
}
If you have a compiler that implements the initalizer-list feature of the draft C++0x standard, you combine your definition and initialization of the map:
std::map<int, std::string> mapping = {{1, "Request System Info"},
{2, "Change System Info"}
{10, "Unkown Error"}};
std::map<> scales well to a large number of entries as std::map<>::find runs in O(log N). Once you have the hash-map feature of the draft C++0x standard, you can easily convert this to a std::unordered_map<> which should be able to look things up in O(1) time.
Qt also provides it's own map implementations - QMap and QHash.
QMap<int, QString> myMap;
myMap[1234] = "Some value";
myMap[5678] = "Another value";
or
myMap.insert(1234, "Some value");
The documentation gives more examples but it's very easy to use.