std::find on vector of objects with overloaded == operator - c++

I am trying to use std::find on the following vector:
std::vector<LoopDetectorData *> Vec_loopDetectors;
And this is how I am using it:
const LoopDetectorData *searchFor = new LoopDetectorData( (*it).c_str(), "", vehicleName.c_str() );
std::vector<LoopDetectorData *>::iterator counter = std::find(Vec_loopDetectors.begin(), Vec_loopDetectors.end(), searchFor);
This is the definition of LoopDetectorData class with operator == overloading.
class LoopDetectorData
{
public:
char detectorName[20];
char lane[20];
char vehicleName[20];
double entryTime;
double leaveTime;
double entrySpeed;
double leaveSpeed;
LoopDetectorData( const char *str1, const char *str2, const char *str3, double entryT=-1, double leaveT=-1, double entryS=-1, double leaveS=-1 )
{
strcpy(this->detectorName, str1);
strcpy(this->lane, str2);
strcpy(this->vehicleName, str3);
this->entryTime = entryT;
this->leaveTime = leaveT;
this->entrySpeed = entryS;
this->leaveSpeed = leaveS;
}
friend bool operator== (const LoopDetectorData &v1, const LoopDetectorData &v2);
};
It seems that std::find can not find an item even if the item exists in the vector.

std::find() searches by value. So it will compare pointers stored in your vector to the pointer which you just created to serve as search argument. This is dommed to fail: you compare pointers and not the values of the object poitned to.
You shall use std::find_if() instead:
auto counter = std::find_if (Vec_loopDetectors.begin(),
Vec_loopDetectors.end(),
[&searchFor](const LoopDetectorData *f)->bool
{ return *f == *searchFor; }
);
find_if uses a predicate which is here an ad-hoc lambda function that compares the values pointed to by dereferencing the pointers. If you're not comfortable with lambdas, you could use a function poitner instead.
Here a live demo of this alternative, with a comparison to you rinitial attempt.

Related

How to sort CTypedPtrList using qsort?

I converted CTypedPtrList to CArray of pointers to object.
For this I added below code.
typedef CTypedPtrList<CPtrList, CMyList*> CTagInfoList;
CMyInfoList* myList;
CArray<CMyList* ,CMyList*> arr;
,,
,,
POSITION pos = myList->GetHeadPosition();
while( pos != NULL ){
CMyList* pObj = (CMyList*)myList->GetNext(pos);
arr.Add(pObj);
}
How to call qsort using CArray of pointers to object.
You can do something like this:
qsort(static_cast<void*>(&arr[0]), arr.GetSize(), sizeof(CMyList*), CompareFunction);
where CompareFunction is a static method:
// header:
static int CompareFunction(const void* a, const void* b);
// cpp
int CYourClass::CompareFunction(const void* a, const void* b)
{
CMyList* pA = static_cast<CMyList*>(a);
CMyList* pB = static_cast<CMyList*>(b);
return pA->Compare(*pB); // Compare is a comparision CMyList* criteria
}
Also, you take a look on qsort reference: http://www.cplusplus.com/reference/cstdlib/qsort/

Sort objects stored in vector by string C++

Imagine class with string value.
class Test {
public:
string name;
};
Is is stored in vector
vector<Test> d;
I would like to use sort() function to sort objects in vector alphabeticaly by its name value. I know that sort() function has third parameter some kind of sorting function but i dont know how to write this function.
sort(d.begin(),d.end(),comp());
comp () { ? }
You can create a comparator
bool comp(const Test &test1, const Test &test2){
return test1.getName() < test2.getName();
}
Or you can overload the operator < in your class
bool operator < (const Test &test){
return name < test.name;
}
Note that if you overload the operator <, then you don't need to add the third parameter in the sort function.
comp will be a function that will return a bool to indicate the comparison in two parameters. In your case, it should look like:
bool compare ( const Test& s1, const Test& s2 )
{
return ( s1.name < s2.name );
}
and your call will be: sort ( d.begin(), d.end(), compare );

Getting wrong result for FIND function of map

The method fails to find the char* array passed to it even though it is present in map.
When I replaced char* with std::string in map. Code works fine.
static void CreateTranslationMap();
static UString FindTranslatedString(char* propertyName);
static std::map<char*,UString> TranslationMap ;
static void CreateTranslationMap()
{
UString engString("TextAlignmentPosition");
char* transString= MSGTXT("TextAlignmentPosition");
TranslationMap.insert(std::pair<char*,UString>(transString,engString));
}
UString FindTranslatedString(char* propertyName)
{
UString NotFound("CannotFind");
std::map<char*, UString>::iterator itr;
itr = TranslationMap.begin();
itr = TranslationMap.find(propertyName);
if(itr!= TranslationMap.end())
{
return itr->second;
}
else if(itr== TranslationMap.end())
{
return NotFound;
}
}
You need to use your own custom comparator for comparing pointer to char
Use:
struct cmp_c_string
{
bool operator()(char const *lhs, char const *rhs) const
{
return std::strcmp(lhs, rhs) < 0;
}
};
std::map<char*,UString, cmp_c_string > TranslationMap ;
That's because when doing comparison for equality the map uses <.
When the Key of the map is char* you are doing comparisons of pointers (not the C-String). So you are testing to see if one pointer is less than the other pointer (ie comparing the address).
When the Key of the map is std::string you using the operator< that is defined for std::string, which actually compares the characters in the string to determine which is less than the other.
As the std::map is a template it actually takes more parameters to define how it works. The third parameters is the comparison function (which defaults to less std::less<K> which is usually operator<).
So you can use char* you just need a custom comparator operator.
bool comparitor(char const* lhs, char const* rhs)
{
return (test for less than or some other strict weak function);
}
std::map<char*, UString, comparitor> myMap;
when using char *, it just compare address.
char a[] = "hi";
char b[] = "hi";
char *c = a;
char *d = b;
c & d are different.(c != d) If you want to compare string, you should use strcmp.
But when using string, it overwrites "==" operation.
So you can just compare using "=="
string a = "hi";
string b = "hi";
a & b are same. (a == b)
You have this behavior because you use pointer to string literal which is different every time you create such a pointer. So, for example, you create 2 pointers:
char* p1 = "Hello world!";
char* p2 = "Hello world!";
While content to which p1 and p2 point is identical the pointers, themselves, are different. So p1 != p2, and you trying to store pointer in the map. You should use std::string instead or have global constants pointers which you'd use everywhere; something like:
const char* const c_transString = MSGTXT("TextAlignmentPosition");
...
TranslationMap.insert(std::pair<char*,UString>(c_transString, engString));
...
FindTranslatedString(c_transString)
Just replace char* to const char* because the map data type always take the string in const form . I took your example and it is running in my terminal. So the new code is :
#include<iostream>
using namespace std;
static void CreateTranslationMap();
static string FindTranslatedString(const char* propertyName);
static std::map<const char*,string> TranslationMap ;
static void CreateTranslationMap()
{
string engString("TextAlignmentPosition");
const char* transString= ("1");
TranslationMap.insert(std::pair<const char*,string>(transString,engString));
}
string FindTranslatedString(const char* propertyName)
{
string NotFound("CannotFind");
std::map<const char*, string>::iterator itr;
itr = TranslationMap.begin();
itr = TranslationMap.find(propertyName);
if(itr!= TranslationMap.end())
{
return itr->second;
}
else if(itr== TranslationMap.end())
{
return NotFound;
}
}
int main()
{
CreateTranslationMap();
string s =FindTranslatedString("1");
cout<<s<<endl;
return 0;
}

Iterating in a sorted manner over a std::vector<std::pair<T,U> > object

I am reading a object from a database of type Foo, as defined below. This object is a vector of Foo Members, where a Foo Members consists of a string id and a container object.
typedef std::pair<std::string, Container> FooMember;
typedef std::vector<FooMember> Foo;
I wish to iterate over a Foo object in its sorted form, where sorting is done with respect to the id. To do this I am using the following function to create first a sorted version of the object. As you can see, the object is sorted in a case insensitive manner. Is there a better way for me to iterate over this object compared to how I am currently doing it?
Foo sortedFoo(Foo& value) const {
Foo returnValue;
returnValue.reserve(value.size());
// use a map to sort the items
std::map<std::string, FooMember> sortedMembers;
{
Foo::iterator i = value.begin();
Foo::iterator end = value.end();
for(; i!=end; ++i) {
std::string name = i->first;
boost::algorithm::to_lower(name);
sortedMembers[name] = *i;
}
}
// convert the map to a vector of its values in sorted order
std::map<std::string, FooMember >::iterator i = sortedMembers.begin();
std::map<std::string, FooMember >::iterator end = sortedMembers.end();
for(; i!=end; ++i) {
returnValue.push_back(i->second);
}
return returnValue;
}
Yes: Copy the vector, then use std::sort with a custom comparison predicate:
struct ByIdCaseInsensitive {
bool operator ()(const FooMember& lhs, const FooMember& rhs) const {
return boost::algorithm::to_lower_copy(lhs.first) <
boost::algorithm::to_lower_copy(rhs.first);
}
};
Way more efficient than filling a map, and then copying back to a vector.
The predicate would be even better if it used a proper Unicode collation algorithm, but that isn't available in the standard library or Boost.
You can use std::sort
#include <algorithm>
bool comparator(const FooMember& i, const FooMember& j)
{
std::string str1 = i.first;
boost::algorithm::to_lower(str1);
std::string str2 = j.first;
boost::algorithm::to_lower(str2);
return (str1 < str2);
}
void sortFoo(Foo& value) {
std::sort (value.begin(), value.end(), comparator);
}
Or, you can keep Foo objects in a std::map<std::string, Foo> from the beginning so they remain always sorted.
The best way would be to use std::sort with a custom comparator for FooMembers:
bool cmp(const FooMember& lhs, const FooMember& rhs);
Foo sortedFoo(const Foo& value) const
{
Foo tmp = value;
return std::sort(tmp.begin(), tmp.end(), cmp);
}
where the comparison can be implemented with the help of std::lexicographical_compare and tolower:
#include <cctype> // for std::tolower
bool ci_cmp(char a, char b)
{
return std::tolower(a) < std::tolower(b);
}
#include <algorithm> // for std::sort, std::lexicographical_compare
bool cmp(const FooMember& lhs, const FooMember& rhs)
{
return std::lexicographical_compare(lhs.first.begin(),
lhs.first.end(),
rhs.first.begin(),
rhs.first.end(),
ci_cmp);
}
You can also use std::sort with a lambda expression:
std::sort(value.begin(), value.end(), [](const FooMember &lhs, const FooMember &rhs)
{
std::string str1 = i.first, str2 = j.first;
boost::algorithm::to_lower(str1);
boost::algorithm::to_lower(str2);
return str1 < str2;
});
Or use the version provided by erelender. It's up to you.
Semantically std::vector<std::pair<T,U> > is a std::map<T,U> (but implementations are usually different). If you can re-design Foo, you probably better do it. As side effect, you will get sorting for free.
typedef std::map<std::string, Container> Foo;

Counting matches in vector of structs

I have a problem that requires me to count the number of instances within this array that uses either std::count() or std::find(). I'm aware of how to do this using a standard data (see bottom code) type but not with the NameContainer that I'm using.
//Type
struct NameContainer{
char name [32];
}
//An array of containers
NameContainer *_storedNames = new NameContainer[_numberOfNames];
//An example of what I'm trying to do with a string rather than the NameContainer
std::vector<string> v(_storedNames, _storedNames + _numberOfNames);
//returns an numeric value
return std::count(v.begin(), v.end(), nameToSearch))
You can use a functor
struct names_equal {
string comp_to;
names_equal(string a) : comp_to(a) {}
bool operator()(NameContainer& p) {
return p.name == comp_to;
}
};
And count like
cout << std::count_if(v.begin(), v.end(), names_equal(nameToSearch));
This way nameToSearch doesn't have to be hard coded.
EDIT
If you can not use count_if, and has to be count then modify NameContainer and overload == for it.
struct NameContainer{
string name;
bool operator==(string str) {
return name == str;
}
};
Then count like this
cout << std::count(v.begin(), v.end(), nameToSearch);
you can use count_if and you provide a predicate (Unary function that accepts an element in the range as argument, and returns a value convertible to bool)
for example
bool myPred(NameContainer n){
return (strcmp(n.name, "name") == 0); }
std::vector<NameContainer> v(_storedNames, _storedNames + _numberOfNames);
int i=std::count_if(v.begin(), v.end(), myPred))
you can use strcmp() to compare character arrays.
if using only std::count or std::find:
both count and find takes the same type argument to compare as the type of conatainer, in your case NameContainer. std::count will execute following to compare searched values:
if (*first == val)
what means you have to overload operator== taking your class as arguments.
inline bool operator == (const NameContainer &first,const NameContainer &second){
return (strcmp(first.name,second.name)==0);
}
and then call std::count(v.begin(), v.end(), myObjectPredicate))
with myObjectPredicate being your NameContainer class object with name to be searched in vector.
so here is working solution. you might improve it in details:
struct NameContainer{
char name [32];
};
inline bool operator== (const NameContainer &first,const NameContainer &second){
return (strcmp(first.name,second.name)==0);
}
int main(int argc, char** argv) {
NameContainer* _storedNames = new NameContainer[1];
std::vector<NameContainer> vn(_storedNames, _storedNames + 1);
const char* cc="piotr";
NameContainer nc;
memcpy(nc.name,cc,strlen(cc)+1);
vn.push_back(nc);
NameContainer myObjectPredicate;
memcpy(myObjectPredicate.name,cc,strlen(cc)+1);
int count=std::count(vn.begin(), vn.end(), myObjectPredicate);
std::cout<<count;
return 2400;
}
output:
1
Read the docs on std::count, you'll see that it uses operator== for it's comparisons. Therefore, if you want to use std::count, the thing you want to compare MUST have an operator== defined for it. In your case, you could add one to your NameContainer pretty easily.