I am trying to find the difference in my code when I use std::find.
For my test code. I made a Vector called Test
std::vector<const char*> Test;
To test the find function, I filled the Test vector with dummy data by using push_back function
Test.push_back("F_S");
Test.push_back("FC");
Test.push_back("ID");
Test.push_back("CD");
Test.push_back("CT");
Test.push_back("DS");
Test.push_back("CR");
Test.push_back("5K_2");
Test.push_back("10K_5");
Test.push_back("10K_1");
Test.push_back("10K_2");
Test.push_back("10K_3");
Test.push_back("10K_4");
Test.push_back("10K_5");
What I want to do with the find function is to go through the Test and see if there are any repeated data. The first time a encounter the data, I will save it to a vector called Unique_Data.
std::vector<const char*> Unique_Data;
So for the 14 data points above, only 13 will be saved because 10K_5 repeated.
The Code I am using looks like this
for(int i = 0; i < Test.size(); i++)
{
if( Unique_Data.empty())
{
Unique_Data.push_back(Test[i]);
}
else if (std::find(Unique_Data.begin(), Unique_Data.end(), Test[i]) != Unique_Data.end())
{
// Move on to next index
}
else
{
Unique_Data.push_back(Test[i]);
}
}
The problem I am having is when I am using the dummy data. I am getting a correct answer for Unique_Data.
However, if I save the actual data into the Test vector which are saved in linked list. I get that they are all unique.
The code looks like this
p_curr = List.p_root;
while(p_curr != NULL)
{
// id starts from 0
if(atoi(p_curr->id) == 14) break;
Test.push_back(p_curr->Descriptor);
p_curr = p_curr->p_next;
}
I tested with the same 14 data. They are all const char* types. However, when I used the linked list data. The find function thinks all the data is unique.
Can anyone tell me what is wrong with this?
Using C-style strings is a bit tricky, they are just a pointer, and pointers are compared by identity. Two C strings with the same sequence of characters, but different addresses will compare different.
const char first[] = "Hi";
const char second[] = "Hi";
assert(first == second); // will fail!
There are two solutions to this problem. The simple one is using std::string in your container, as std::string will provide value comparisons. The alternative is to pass a comparison functor to std::find as a last argument. But this will still leave the problem of managing the lifetime of the const char*-s stored in the vector.
This is a pointers problem. You're not storing strings in your array, you're storing the memory address of the data in the string.
This strange behaviour is probably because in your example case you have literal strings that cannot be changed, so the compiler is optimising the storage, and when two strings are the same then it stores the same address for all strings that have the same text.
In your real data example, you have a bunch of strings that hold the same data, but each of these strings lives at a different memory address, so the find function is saying that all strings have a different address.
In summary, your find function is looking at the memory address of the string, not the data (text) in the string. If you use std::strings then this problem will disappear.
I would highly recommend using strings, as performance is going to be more than good enough and they eliminate a vast number of problems.
As David Rodriguez mentions in his answer, you're only comparing pointers, and not the contents of the strings themselves. Your solution will work as is if you were storing std::strings instead of char const *. With the latter, you need to resort to std::find_if and a predicate that calls strcmp to determine whether the strings are identical.
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
int main()
{
std::vector<const char*> Test;
Test.push_back("F_S");
Test.push_back("FC");
Test.push_back("ID");
Test.push_back("CD");
Test.push_back("CT");
Test.push_back("DS");
Test.push_back("CR");
Test.push_back("5K_2");
Test.push_back("10K_5");
Test.push_back("10K_1");
Test.push_back("10K_2");
Test.push_back("10K_3");
Test.push_back("10K_4");
Test.push_back("10K_5");
std::vector<const char*> Unique_Data;
for(auto const& s1 : Test) {
if(std::find_i(Unique_Data.cbegin(), Unique_Data.cend(),
[&](const char *s2) { return std::strcmp(s1, s2) == 0; })
== Unique_Data.cend()) {
Unique_Data.push_back(s1);
}
}
for(auto const& s : Unique_Data) {
std::cout << s << '\n';
}
}
Here's a live example
Related
I am trying to figure out why the following code is not working, and I am assuming it is an issue with using char* as the key type, however I am not sure how I can resolve it or why it is occuring. All of the other functions I use (in the HL2 SDK) use char* so using std::string is going to cause a lot of unnecessary complications.
std::map<char*, int> g_PlayerNames;
int PlayerManager::CreateFakePlayer()
{
FakePlayer *player = new FakePlayer();
int index = g_FakePlayers.AddToTail(player);
bool foundName = false;
// Iterate through Player Names and find an Unused one
for(std::map<char*,int>::iterator it = g_PlayerNames.begin(); it != g_PlayerNames.end(); ++it)
{
if(it->second == NAME_AVAILABLE)
{
// We found an Available Name. Mark as Unavailable and move it to the end of the list
foundName = true;
g_FakePlayers.Element(index)->name = it->first;
g_PlayerNames.insert(std::pair<char*, int>(it->first, NAME_UNAVAILABLE));
g_PlayerNames.erase(it); // Remove name since we added it to the end of the list
break;
}
}
// If we can't find a usable name, just user 'player'
if(!foundName)
{
g_FakePlayers.Element(index)->name = "player";
}
g_FakePlayers.Element(index)->connectTime = time(NULL);
g_FakePlayers.Element(index)->score = 0;
return index;
}
You need to give a comparison functor to the map otherwise it's comparing the pointer, not the null-terminated string it points to. In general, this is the case anytime you want your map key to be a pointer.
For example:
struct cmp_str
{
bool operator()(char const *a, char const *b) const
{
return std::strcmp(a, b) < 0;
}
};
map<char *, int, cmp_str> BlahBlah;
You can't use char* unless you are absolutely 100% sure you are going to access the map with the exact same pointers, not strings.
Example:
char *s1; // pointing to a string "hello" stored memory location #12
char *s2; // pointing to a string "hello" stored memory location #20
If you access map with s1 you will get a different location than accessing it with s2.
Two C-style strings can have equal contents but be at different addresses. And that map compares the pointers, not the contents.
The cost of converting to std::map<std::string, int> may not be as much as you think.
But if you really do need to use const char* as map keys, try:
#include <functional>
#include <cstring>
struct StrCompare : public std::binary_function<const char*, const char*, bool> {
public:
bool operator() (const char* str1, const char* str2) const
{ return std::strcmp(str1, str2) < 0; }
};
typedef std::map<const char*, int, StrCompare> NameMap;
NameMap g_PlayerNames;
You can get it working with std::map<const char*, int>, but must not use non-const pointers (note the added const for the key), because you must not change those strings while the map refers to them as keys. (While a map protects its keys by making them const, this would only constify the pointer, not the string it points to.)
But why don't you simply use std::map<std::string, int>? It works out of the box without headaches.
You are comparing using a char * to using a string. They are not the same.
A char * is a pointer to a char. Ultimately, it is an integer type whose value is interpreted as a valid address for a char.
A string is a string.
The container works correctly, but as a container for pairs in which the key is a char * and the value is an int.
As the others say, you should probably use std::string instead of a char* in this case although there is nothing wrong in principle with a pointer as a key if that's what is really required.
I think another reason this code isn't working is because once you find an available entry in the map you attempt to reinsert it into the map with the same key (the char*). Since that key already exists in your map, the insert will fail. The standard for map::insert() defines this behaviour...if the key value exists the insert fails and the mapped value remains unchanged. Then it gets deleted anyway. You'd need to delete it first and then reinsert.
Even if you change the char* to a std::string this problem will remain.
I know this thread is quite old and you've fixed it all by now but I didn't see anyone making this point so for the sake of future viewers I'm answering.
Had a hard time using the char* as the map key when I try to find element in multiple source files. It works fine when all the accessing/finding within the same source file where the elements are inserted. However, when I try to access the element using find in another file, I am not able to get the element which is definitely inside the map.
It turns out the reason is as Plabo pointed out, the pointers (every compilation unit has its own constant char*) are NOT the same at all when it is accessed in another cpp file.
std::map<char*,int> will use the default std::less<char*,int> to compare char* keys, which will do a pointer comparison. But you can specify your own Compare class like this:
class StringPtrCmp {
public:
StringPtrCmp() {}
bool operator()(const char *str1, const char *str2) const {
if (str1 == str2)
return false; // same pointer so "not less"
else
return (strcmp(str1, str2) < 0); //string compare: str1<str2 ?
}
};
std::map<char*, YourType, StringPtrCmp> myMap;
Bear in mind that you have to make sure that the char* pointer are valid.
I would advice to use std::map<std::string, int> anyway.
You can bind a lambda that does the same job.
#include <map>
#include <functional>
class a{
std::map < const char*, Property*, std::function<bool(const char* a, const char* b)> > propertyStore{
std::bind([](const char* a, const char* b) {return std::strcmp(a,b) < 0;},std::placeholders::_1,std::placeholders::_2)
};
};
There's no problem to use any key type as long as it supports comparison (<, >, ==) and assignment.
One point that should be mentioned - take into account that you're using a template class. As the result compiler will generate two different instantiations for char* and int*. Whereas the actual code of both will be virtually identical.
Hence - I'd consider using a void* as a key type, and then casting as necessary.
This is my opinion.
I am trying to figure out why the following code is not working, and I am assuming it is an issue with using char* as the key type, however I am not sure how I can resolve it or why it is occuring. All of the other functions I use (in the HL2 SDK) use char* so using std::string is going to cause a lot of unnecessary complications.
std::map<char*, int> g_PlayerNames;
int PlayerManager::CreateFakePlayer()
{
FakePlayer *player = new FakePlayer();
int index = g_FakePlayers.AddToTail(player);
bool foundName = false;
// Iterate through Player Names and find an Unused one
for(std::map<char*,int>::iterator it = g_PlayerNames.begin(); it != g_PlayerNames.end(); ++it)
{
if(it->second == NAME_AVAILABLE)
{
// We found an Available Name. Mark as Unavailable and move it to the end of the list
foundName = true;
g_FakePlayers.Element(index)->name = it->first;
g_PlayerNames.insert(std::pair<char*, int>(it->first, NAME_UNAVAILABLE));
g_PlayerNames.erase(it); // Remove name since we added it to the end of the list
break;
}
}
// If we can't find a usable name, just user 'player'
if(!foundName)
{
g_FakePlayers.Element(index)->name = "player";
}
g_FakePlayers.Element(index)->connectTime = time(NULL);
g_FakePlayers.Element(index)->score = 0;
return index;
}
You need to give a comparison functor to the map otherwise it's comparing the pointer, not the null-terminated string it points to. In general, this is the case anytime you want your map key to be a pointer.
For example:
struct cmp_str
{
bool operator()(char const *a, char const *b) const
{
return std::strcmp(a, b) < 0;
}
};
map<char *, int, cmp_str> BlahBlah;
You can't use char* unless you are absolutely 100% sure you are going to access the map with the exact same pointers, not strings.
Example:
char *s1; // pointing to a string "hello" stored memory location #12
char *s2; // pointing to a string "hello" stored memory location #20
If you access map with s1 you will get a different location than accessing it with s2.
Two C-style strings can have equal contents but be at different addresses. And that map compares the pointers, not the contents.
The cost of converting to std::map<std::string, int> may not be as much as you think.
But if you really do need to use const char* as map keys, try:
#include <functional>
#include <cstring>
struct StrCompare : public std::binary_function<const char*, const char*, bool> {
public:
bool operator() (const char* str1, const char* str2) const
{ return std::strcmp(str1, str2) < 0; }
};
typedef std::map<const char*, int, StrCompare> NameMap;
NameMap g_PlayerNames;
You can get it working with std::map<const char*, int>, but must not use non-const pointers (note the added const for the key), because you must not change those strings while the map refers to them as keys. (While a map protects its keys by making them const, this would only constify the pointer, not the string it points to.)
But why don't you simply use std::map<std::string, int>? It works out of the box without headaches.
You are comparing using a char * to using a string. They are not the same.
A char * is a pointer to a char. Ultimately, it is an integer type whose value is interpreted as a valid address for a char.
A string is a string.
The container works correctly, but as a container for pairs in which the key is a char * and the value is an int.
As the others say, you should probably use std::string instead of a char* in this case although there is nothing wrong in principle with a pointer as a key if that's what is really required.
I think another reason this code isn't working is because once you find an available entry in the map you attempt to reinsert it into the map with the same key (the char*). Since that key already exists in your map, the insert will fail. The standard for map::insert() defines this behaviour...if the key value exists the insert fails and the mapped value remains unchanged. Then it gets deleted anyway. You'd need to delete it first and then reinsert.
Even if you change the char* to a std::string this problem will remain.
I know this thread is quite old and you've fixed it all by now but I didn't see anyone making this point so for the sake of future viewers I'm answering.
Had a hard time using the char* as the map key when I try to find element in multiple source files. It works fine when all the accessing/finding within the same source file where the elements are inserted. However, when I try to access the element using find in another file, I am not able to get the element which is definitely inside the map.
It turns out the reason is as Plabo pointed out, the pointers (every compilation unit has its own constant char*) are NOT the same at all when it is accessed in another cpp file.
std::map<char*,int> will use the default std::less<char*,int> to compare char* keys, which will do a pointer comparison. But you can specify your own Compare class like this:
class StringPtrCmp {
public:
StringPtrCmp() {}
bool operator()(const char *str1, const char *str2) const {
if (str1 == str2)
return false; // same pointer so "not less"
else
return (strcmp(str1, str2) < 0); //string compare: str1<str2 ?
}
};
std::map<char*, YourType, StringPtrCmp> myMap;
Bear in mind that you have to make sure that the char* pointer are valid.
I would advice to use std::map<std::string, int> anyway.
You can bind a lambda that does the same job.
#include <map>
#include <functional>
class a{
std::map < const char*, Property*, std::function<bool(const char* a, const char* b)> > propertyStore{
std::bind([](const char* a, const char* b) {return std::strcmp(a,b) < 0;},std::placeholders::_1,std::placeholders::_2)
};
};
There's no problem to use any key type as long as it supports comparison (<, >, ==) and assignment.
One point that should be mentioned - take into account that you're using a template class. As the result compiler will generate two different instantiations for char* and int*. Whereas the actual code of both will be virtually identical.
Hence - I'd consider using a void* as a key type, and then casting as necessary.
This is my opinion.
I made the following program where there is a struct
struct data
{
int integers; //input of integers
int times; //number of times of appearance
}
and there is a vector of this struct
std::vector<data> inputs;
and then I'll get from a file an integer of current_int
std::fstream openFile("input.txt")
int current_int; //current_int is what I want to check if it's in my vector of struct (particularly in inputs.integers)
openFile >> current_int;
and I wanna check if current_int is already stored in my vector inputs.
I've tried researching about finding data in a vector and supposedly you use an iterator like this:
it = std::find(inputs.begin(),inputs.end(),current_int)
but will this work if it's in a struct? Please help.
There are two variants of find:
find() searches for a plain value. In you case you have a vector of data, so the values passed to find() should be data.
find_if() takes a predicate, and returns the first position where the predicates returns true.
Using the latter, you can easily match one field of your struct:
auto it = std::find_if(inputs.begin(), inputs.end(),
[current_int] (const data& d) {
return d.integers == current_int;
});
Note that the above uses a C++11 lambda function. Doing this in earlier versions of C++ requires you to create a functor instead.
How can input a word and reverse the output of it. I made a function to calculate the length of the word and from here I have to reverse the word depending on the length of it.
How can I do that?
#include<iostream>
using std::cout;
using std::endl;
using std::cin;
int LengthOfString( const char *); // declaring prototype for length of the string
int reverse(const char []);
int main()
{
char string1[100];
cout<<"Enter a string: ";
cin>>string1;
cout<<"Length of string is "<<LengthOfString(string1);
system("PAUSE");
return 0;
}
int LengthOfString( const char *x)
{
int index;
for(index = 0; *x!='\0';x++,index++);
return index;
}
int reverse(const char y[])
{
/* my attempted loop, its not right i know.
a[] = *index; // length of the word
for(int i=0; i<=index/2; i++)
for(j=0; j == length, j--) */
}
This wheel has already been invented, and exists in the standard library.
#include <iostream>
#include <string>
#include <algorithm>
int main()
{
std::string word;
std::cout << "Enter a word: ";
std::cin >> word;
std::reverse(word.begin(), word.end());
std::cout << "Reverse: " << word << std::endl;
return 0;
}
To understand exactly what's going on here, there are a few things that you must cover first:
data structures (classes)
containers
iterators
I hope you already know what a class is. In case you're still in introductory stuff, a class is basically a user defined collection of state and behavior. The author can choose to restrict access to the state or behavior of a class for a variety of reasons. In the case of std::string, the standard library string class, all of the state is hidden and only behavior is accessible.
The string class is a container that contains characters. There are numerous other container classes, each of which with different strengths and weaknesses. The string class contains a sequence of characters with a strict order. Other containers exist, such as std::set, std::vector, std::list, and others. std::string bears a passing resemblance to std::vector, and is a distant cousin of std::list. Each collection behaves differently and is suited for different things.
You might think you need to understand how the string class stores its data in order to reverse it, but you don't. This is where iterators come in. std::string owns a typedef, std::string::iterator, which is a special object which stores the location of a single element in a string. std::reverse is a library function which takes 2 iterators and repeatedly swaps their contents and moves them towards each other. This looks like this as it's happening:
v v <-- positions of iterators (start at the start, end at the end)
ABC <-- initial state
v v <-- the end iterator moved back
ABC
v v
CBA <-- the iterators swapped their values
vv <-- the begin iterator moved forward
CBA
V <-- the end iterator moved back; both iterators are in the same place
CBA <-- therefore, we're done, the string is reversed
One thing about iterators is they're kind of like pointers. In fact, you can pass pointers to some functions that expect iterators because they behave syntactically the same. Therefore, you should be able to write your own reverse function that uses pointers that basically does the same thing this did, except with char *s.
Here's some pseudocode that you should be able to write the function with (I won't write it out completely because it's homework):
namespace BaidNation
{
void reverse(char *begin, char *end)
{
loop forever
{
if (end equals begin):
done;
move end backwards;
if (end equals begin):
done;
swap end's and begin's characters;
move begin forwards;
}
}
}
Keep in mind that BaidNation::reverse (as well as std::reverse) expects for end the iterator that references the element AFTER the end of the collection, not the one that references the last element. How does it then make sense to use this?
Your LengthOfString function returns the number of non-null characters in a string. Since arrays are zero-indexed, we know that, like any other array, if we check string1 + LengthOfString(string1), we'll get a pointer to the character after the end which is, for once, exactly what we want.
Thus, we can use this to reverse the string:
BaidNation::reverse(string1, string1 + LengthOfString(string1));
If you have to use exactly the signature earlier, you can adapt this design into the other one:
int reverse(const char str[])
{
char *start = str, *end = str + LengthOfString(str);
BaidNation::reverse(start, end);
}
Based on the fact that the return type of your prototype function is int, it looks to me like you want to do an in-place reversal of a string. You first need to find out how long the string is (although you computed that before, you didn't pass the result to this function), then swap elements until you get to the middle. To make this work you need to pass, not a const char[], but just a char* (indicating that you will be changing the content):
int reverse(char* y)
{
int ii, n;
n = LengthOfString(y); // "no built in functions - otherwise, use strlen()
for(ii=0; ii<n/2;ii++) {
char temp;
temp = y[ii];
y[ii] = y[n - ii - 1];
y[n - ii] = temp;
}
}
Declare a new char* of the same length, and then loop as follows -
for(int i=0;i<stringLength;i++){
newString[i]=oldString[stringLength-i];
}
return newString;
Also you might want to consider using the String class instead of char*.
I am trying to figure out why the following code is not working, and I am assuming it is an issue with using char* as the key type, however I am not sure how I can resolve it or why it is occuring. All of the other functions I use (in the HL2 SDK) use char* so using std::string is going to cause a lot of unnecessary complications.
std::map<char*, int> g_PlayerNames;
int PlayerManager::CreateFakePlayer()
{
FakePlayer *player = new FakePlayer();
int index = g_FakePlayers.AddToTail(player);
bool foundName = false;
// Iterate through Player Names and find an Unused one
for(std::map<char*,int>::iterator it = g_PlayerNames.begin(); it != g_PlayerNames.end(); ++it)
{
if(it->second == NAME_AVAILABLE)
{
// We found an Available Name. Mark as Unavailable and move it to the end of the list
foundName = true;
g_FakePlayers.Element(index)->name = it->first;
g_PlayerNames.insert(std::pair<char*, int>(it->first, NAME_UNAVAILABLE));
g_PlayerNames.erase(it); // Remove name since we added it to the end of the list
break;
}
}
// If we can't find a usable name, just user 'player'
if(!foundName)
{
g_FakePlayers.Element(index)->name = "player";
}
g_FakePlayers.Element(index)->connectTime = time(NULL);
g_FakePlayers.Element(index)->score = 0;
return index;
}
You need to give a comparison functor to the map otherwise it's comparing the pointer, not the null-terminated string it points to. In general, this is the case anytime you want your map key to be a pointer.
For example:
struct cmp_str
{
bool operator()(char const *a, char const *b) const
{
return std::strcmp(a, b) < 0;
}
};
map<char *, int, cmp_str> BlahBlah;
You can't use char* unless you are absolutely 100% sure you are going to access the map with the exact same pointers, not strings.
Example:
char *s1; // pointing to a string "hello" stored memory location #12
char *s2; // pointing to a string "hello" stored memory location #20
If you access map with s1 you will get a different location than accessing it with s2.
Two C-style strings can have equal contents but be at different addresses. And that map compares the pointers, not the contents.
The cost of converting to std::map<std::string, int> may not be as much as you think.
But if you really do need to use const char* as map keys, try:
#include <functional>
#include <cstring>
struct StrCompare : public std::binary_function<const char*, const char*, bool> {
public:
bool operator() (const char* str1, const char* str2) const
{ return std::strcmp(str1, str2) < 0; }
};
typedef std::map<const char*, int, StrCompare> NameMap;
NameMap g_PlayerNames;
You can get it working with std::map<const char*, int>, but must not use non-const pointers (note the added const for the key), because you must not change those strings while the map refers to them as keys. (While a map protects its keys by making them const, this would only constify the pointer, not the string it points to.)
But why don't you simply use std::map<std::string, int>? It works out of the box without headaches.
You are comparing using a char * to using a string. They are not the same.
A char * is a pointer to a char. Ultimately, it is an integer type whose value is interpreted as a valid address for a char.
A string is a string.
The container works correctly, but as a container for pairs in which the key is a char * and the value is an int.
As the others say, you should probably use std::string instead of a char* in this case although there is nothing wrong in principle with a pointer as a key if that's what is really required.
I think another reason this code isn't working is because once you find an available entry in the map you attempt to reinsert it into the map with the same key (the char*). Since that key already exists in your map, the insert will fail. The standard for map::insert() defines this behaviour...if the key value exists the insert fails and the mapped value remains unchanged. Then it gets deleted anyway. You'd need to delete it first and then reinsert.
Even if you change the char* to a std::string this problem will remain.
I know this thread is quite old and you've fixed it all by now but I didn't see anyone making this point so for the sake of future viewers I'm answering.
Had a hard time using the char* as the map key when I try to find element in multiple source files. It works fine when all the accessing/finding within the same source file where the elements are inserted. However, when I try to access the element using find in another file, I am not able to get the element which is definitely inside the map.
It turns out the reason is as Plabo pointed out, the pointers (every compilation unit has its own constant char*) are NOT the same at all when it is accessed in another cpp file.
std::map<char*,int> will use the default std::less<char*,int> to compare char* keys, which will do a pointer comparison. But you can specify your own Compare class like this:
class StringPtrCmp {
public:
StringPtrCmp() {}
bool operator()(const char *str1, const char *str2) const {
if (str1 == str2)
return false; // same pointer so "not less"
else
return (strcmp(str1, str2) < 0); //string compare: str1<str2 ?
}
};
std::map<char*, YourType, StringPtrCmp> myMap;
Bear in mind that you have to make sure that the char* pointer are valid.
I would advice to use std::map<std::string, int> anyway.
You can bind a lambda that does the same job.
#include <map>
#include <functional>
class a{
std::map < const char*, Property*, std::function<bool(const char* a, const char* b)> > propertyStore{
std::bind([](const char* a, const char* b) {return std::strcmp(a,b) < 0;},std::placeholders::_1,std::placeholders::_2)
};
};
There's no problem to use any key type as long as it supports comparison (<, >, ==) and assignment.
One point that should be mentioned - take into account that you're using a template class. As the result compiler will generate two different instantiations for char* and int*. Whereas the actual code of both will be virtually identical.
Hence - I'd consider using a void* as a key type, and then casting as necessary.
This is my opinion.