I'm testing std::set with a custom comparator. But I see the same object getting inserted twice.
Following is the object class:
class Info
{
public:
Info(string n, string oN, int dom):
name(n),
objName(oN),
domain(dom)
{}
void setName(std::string n) { name = n;}
void setObjName(std::string n) { objName = n;}
void setDomain(int dom) { domain = dom; }
std::string getName() const { return name;}
std::string getObjName() const { return objName;}
int getDomain() const { return domain;}
private:
std::string name;
std::string objName;
int domain;
};
Following is my custom comparator:
struct InfoCmp {
bool operator() (const Info &lhs, const Info &rhs) const {
if((lhs.getName() < rhs.getName()) || (lhs.getObjName() < rhs.getObjName()) || (lhs.getDomain() < rhs.getDomain()) ){
return true;
}
return false;
}
};
Following is the usage:
Info rst1("rst1", "rstObj1", 1);
Info rst2("rst2", "rstObj2", 2);
Info rst3("rst1", "rstObj3", 3);
std::set<Info,InfoCmp> resetSet;
resetSet.insert(rst1);
resetSet.insert(rst2);
resetSet.insert(rst3);
resetSet.insert(rst1);
resetSet.insert(rst2);
resetSet.insert(rst3);
I see rst2 inserted twice, but it shouldn't be as per my comparator.
I see that you've come up with your own solution, after recognizing from the comments that your original did not impose a strict object ordering as required by set. Here's a different version that only requires operator< and not operator==, making it consistent with the classes and algorithms of the standard library. It also simplifies things if you're e.g. doing a case insensitive comparison.
struct InfoCmp {
bool operator() (const Info &lhs, const Info &rhs) const {
if(lhs.getName() < rhs.getName())
return true;
if(rhs.getName() < lhs.getName())
return false;
if(lhs.getObjName() < rhs.getObjName())
return true;
if(rhs.getObjName() < lhs.getObjName())
return false;
return lhs.getDomain() < rhs.getDomain();
}
};
struct InfoCmp2 {
bool operator() (const Info &lhs, const Info &rhs) const {
return std::make_tuple(lhs.getName(), lhs.getObjName(), lhs.getDomain()) < std::make_tuple(rhs.getName(), rhs.getObjName(), rhs.getDomain());
}
};
This operator can written with make_tuple as well, and working fine.
as suggested by Cory Kramer, adding strict ordering in comparator fixed the problem.
struct InfoCmp {
bool operator() (const Info &lhs, const Info &rhs) const {
if((lhs.getName() < rhs.getName())
|| ( (lhs.getName() == rhs.getName()) && (lhs.getObjName() < rhs.getObjName()) )
|| ( (lhs.getName() == rhs.getName()) && (lhs.getObjName() == rhs.getObjName()) && (lhs.getDomain() < rhs.getDomain()) )){
return true;
}
return false;
}
};
Related
Say I have a struct of Items that I'm storing in an std::set and sorting like so:
struct Position
{
int x;
int y;
}
struct Item
{
std::string id;
Position position;
// NOTE: only `position` should matter for equality
operator==(const Item& other)
{
return position == position;
}
};
inline bool operator<(const Item& lhs, const Item& rhs)
{
if (lhs.position.x == rhs.position.x)
{
return lhs.position.y < rhs.position.y;
}
return lhs.position.x < rhs.position.x;
}
using ItemSet = std::set<Item>;
I want to use std::equal_range to search an ItemSet, except I want to search by Position. I know I could do something like:
ItemSet items;
Item tempItem;
tempItem.position = some_position;
auto result = std::equal_range(items.begin(), items.end(), tempItem);
But I would like to avoid the temporary Item.
I tried to use boost::transform_terator like so:
auto tr = [](const Item& item) { return item.pos; };
auto tr_begin = boost::make_transform_iterator(items.begin(), tr);
auto tr_end = boost::make_transform_iterator(items.end(), tr);
Position findme { 2, 1 };
auto result = std::equal_range(tr_begin, tr_end, findme);
But this doesn't compile for reasons I don't understand, and also even if it did work, how would I get an iterator into the original collection from result? Or maybe there's a better way overall to do this?
Here is a test harness show the problem: http://cpp.sh/3hzsq
Any help would be appreciated!
You can use std::set::find with a different type to avoid constructing an Item. Note that your set can only contain one item with a specific position.
You can either make Position directly comparable with Item (Add Item{} < Position{} and Position{} < Item{}) or create a new proxy class:
struct ItemPosition {
Position p;
};
inline bool operator<(const ItemPosition& l, const Item& r) {
return l.position.x == r.position.x ? l.position.y < r.position.y : l.position.x < r.position.x;
};
inline bool operator<(const Item& l, const ItemPosition& r) {
return l.position.x == r.position.x ? l.position.y < r.position.y : l.position.x < r.position.x;
};
// Change the comparator so it can compare with `ItemPosition` too
using ItemSet = std::set<Item, std::less<>>;
You can alternatively use a completely different comparator to make Position comparable with Item.
struct ItemComparator {
bool operator()(const Position& l, const Position& r) const {
return l.x == r.x ? l.y < r.y : l.x < r.x;
}
bool operator()(const Item& l, const Item& r) const {
return operator()(l.position, r.position);
}
bool operator()(const Item& l, const Position& r) const {
return operator()(l.position, r);
}
bool operator()(const Position& l, const Item& r) const {
return operator()(l, r.position);
}
using is_transparent = void;
};
using ItemSet = std::set<Item, ItemComparator>;
And use it like so:
Position findme { 2, 1 };
// Or just `items.find(findme)` if using a custom comparator
auto result = items.find(ItemPosition{ findme });
if (result == items.end()) {
// No item found
} else {
Item& item = *result;
// found item
}
For some reason std::map does not find my objects.
Here is my simplified object :
class LangueISO3 {
public:
enum {
SIZE_ISO3 = 3
};
static constexpr char DEF_LANG[SIZE_ISO3] = {'-','-','-'};
constexpr LangueISO3():code() {
for(size_t i(0); i < SIZE_ISO3; i++){
code[i] = DEF_LANG[i];
}
};
LangueISO3(const std::string& s) {strncpy(code, s.c_str(), 3);};
bool operator==(const LangueISO3& lg) const { return strncmp(code, lg.code, 3) == 0;};
bool operator<(const LangueISO3& lg)const { return code < lg.code;};
private:
char code[SIZE_ISO3];
};
My test is :
{
CPPUNIT_ASSERT_EQUAL(LangueISO3("eng"), LangueISO3("eng"));
std::map<LangueISO3, int> lmap;
lmap.emplace(LangueISO3("fra"), 0);
lmap.emplace(LangueISO3("deu"), 1);
lmap.emplace(LangueISO3("eng"), 2);
auto it = lmap.find(LangueISO3("deu"));
CPPUNIT_ASSERT_EQUAL(1, it->second);
}
The first test has no problem, however the second fails. lmap.find() always return lmap.end()
What did I do wrong ?
You can't compare character arrays with the < operator. When you write code < lg.code, the compiler will compare the address of the code array, and not the contents of the arrays.
Change the definition for operator< to use strncmp:
bool operator<(const LangueISO3& lg)const {
return strncmp(code, lg.code, SIZE_ISO3) < 0;
}
Also, the comparison for operator== should use the SIZE_ISO3 constant instead of hardcoding the size at 3.
I need to know how to overload the operator = != and < so I could work with set<set<object> >
I have my class:
class pr{
private:
set<set<e> > pr_;
public:
pr();
~pr();
void set_pr(set<e> a);
pr& operator=(const pr& a) const;
bool operator!=(const pr& a) const;
bool operator<(const pr& a) const;
};
So if I have a set like this: {{1,2,3},{4,5}} where the numbers are objects.
I would like to do operations with the sets in other class like this:
void otherclass::myfunction(){
pr prt; //I create the objects
pr prt_old;
set<e> C1; //I create the subset and fill
set<e> C2;
//C1 and C2 filled here
prt.set_pr(C1); //Insert the set<e> into set<set<e>>
prt.set_pr(C2); //It will fail because i dont know how to compare set<e> < <set<e>
while(prt != prt_old){
prt_old = prt ;
prt = create(prt_old);
}
//...
I tried to overload doing this:
pr& pr::operator=(const pr& a) const{
this->clear();
for(set<set<e> >::iterator it =a.begin();it!=a.end();it++){
for(set<e>::iterator j = it->begin(); j != it->end();j++){
this->set_pr(*j);
}
}
return *this;
}
bool pr::operator!=(const pr& a) const{
if(this->size() != a.size()){
return 1;
}
//Now i don't know how to continue for check if for example
// i had {{1,2},{3,4}} and {{1},{2}}
//this two set have same size but they are differnt
//How could i just iterate through the two sets at same time
// and check if subset have too same size or if the objects inside the subset are equal
//Also i need the operator < to insert a set<e> into a set<set<e> > but how??
//Note: class 'e' has already defined the "operator<" for when I insert objects in the set<e>
//And i order them by a function that return an integrer
To test if one set is contained in the other, you iterate over each member of the first set, and test if it exists in the second set.
bool operator<(const pr& a) const {
for (auto _set : _data) {
if (a._data.find(_set) == a._data.end())
return false;
}
return true;
}
To test if two sets are identical, you test their size is equal, and that one is contained in the other
bool operator==(const pr& a) const {
return _data.size() == a._data.size() && *this < a;
}
But notice that there is no need to define an operator==, because the default one defined by std::set is fine.
Here is a full functioning program:
#include <iostream>
#include <set>
using namespace std;
template <class e>
class pr {
private:
set<set<e> > _data;
public:
void insert(set<e> a) { _data.insert(a); }
bool operator==(const pr& a) const {
return _data.size() == a._data.size() && *this < a;
}
bool operator!=(const pr& a) const { return !(*this == a); }
bool operator<(const pr& a) const {
for (auto _set : _data) {
if (a._data.find(_set) == a._data.end())
return false;
}
return true;
}
};
int main()
{
pr<int> a,b,c;
a.insert(set<int>({ 1 }));
b.insert(set<int>({ 1 }));
b.insert(set<int>({ 1, 2 }));
c.insert(set<int>({ 1, 2 }));
c.insert(set<int>({ 1 }));
std::cout << ((a<b) ? "a<b\n" : "NOT a<b\n");
std::cout << ((b<a) ? "b<a\n" : "NOT b<a\n");
std::cout << ((a==c) ? "a==c\n" : "NOT a==c\n");
std::cout << ((b==c) ? "b==c\n" : "NOT b==c\n");
std::cout << ((a==b) ? "a==b\n" : "NOT a==b\n");
return 0;
}
I have a little problem, which the Visual Studio compiler don't seem to be bothered by, but do in eclipse and g++.
I have 2 classes, Card and CardDeck. I do have the operator <= for 2 Carddecks as parameters and do not for 2 cards. I have a converting ctor, which converts Card to a Deck.
So the problem is when I do:
card1 <= card2
Which does work fine in visual, as it converts the left part to deck, then converts the right and then does the comparing.
In g++ it says:
no match for 'operator<=' (operand types are 'Card' and 'Card')
But there shouldn't be one. as I said I want the convert ctor would convet both sides and do the compare?
Any explanation and solution for this ?
Edit(the operator and ctor declaration and code):
CardDeck(const Card&);
friend bool operator<=(const CardDeck&, const CardDeck&);
CardDeck::CardDeck(const Card& card){
_Deck.push_back(card);
}
Here's a quick&dirty of what I think you are trying to do:
#include <iostream>
struct Element
{
int x;
Element() :x(0) {}
virtual ~Element() {}
};
struct Container
{
Element elems[10];
int n;
Container() { n=0; }
Container(const Element &e) { elems[0]=e; n=1; }
virtual ~Container() {}
friend bool operator<=(const Container &l, const Container &r);
};
bool operator<=(const Container &l, const Container &r)
{
if (l.n<=r.n) { std::cout << "less/equ\n"; return true; }
else { std::cout << "greater\n"; return false; }
}
int main(int argc, const char *argv[])
{
//Container container;
Element a, b;
if (a<=b) std::cout << "okay\n"; else std::cout << "fail\n";
return 0;
}
It works fine with gcc.
Deck(card1) <= Deck(card2)
Make operator<= (operand types are 'Card' and 'Card') override cause you use(mean) that in card1 <= card2 expression. Your code have architecture errors.
Update:
struct Card
{
bool operator<( const Card& right ) const;
};
struct Deck
{
std::vector<Card> m_arr;
Deck() = default;
Deck( const Card& conversion )
{
m_arr.push_back( conversion );
}
bool operator<( const Deck& right ) const;
};
bool Card::operator<( const Card& right ) const
{
return false;
}
bool Deck::operator<( const Deck& right ) const
{
bool result = false;
if(m_arr.size() < right.m_arr.size())
{
result = true;
}
else if(!m_arr.empty() && m_arr.size() == right.m_arr.size() )
{
result = true;
std::vector<Card>::const_iterator it = right.m_arr.begin();
std::vector<Card>::const_iterator it2 = m_arr.begin();
for(; it2 != m_arr.end(); ++it, ++it2 )
{
if((*it) < (*it2))
{
result = false;
break;
}
}
}
return result;
}
I would like to know if there is an efficient way to remove objects from a container based on values of member fields of the objects. For example, I can do the following using stl::unique with a list of strings:
#include<iostream>
#include<list>
#include<string>
#include<algorithm>
using namespace std;
bool stringCompare(const string & l, const string & r)
{
return (l==r);
}
int main()
{
list<string> myStrings;
myStrings.push_back("1001");
myStrings.push_back("1001");
myStrings.push_back("81");
myStrings.push_back("1001");
myStrings.push_back("81");
myStrings.sort();
myStrings.erase(unique(myStrings.begin(), myStrings.end(), stringCompare), myStrings.end());
list<string>::iterator it;
for(it = myStrings.begin(); it != myStrings.end(); ++it)
{
cout << *it << endl;
}
return 0;
}
prints 1001, 81...
Is there a way I can do something similar with the following code, or do I need to perform the comparisons "manually" using operators and iterating through the containers. I couldn't think of a more elegant solution and would like to know if this is possible without writing a lot of code. Any help will be much appreciated!
class Packet
{
public:
Packet(string fTime, string rID) : filingTime(fTime), recordID(rID)
string getFilingTime() {return filingTime;}
string getRecordId() {return recordID;}
private:
string filingTime;
string recordID;
};
int main()
{
vector<Packet*> pkts;
pkts.push_back(new Packet("10:20", "1004"));
pkts.push_back(new Packet("10:20", "1004")); // not unique (duplicate of the line above)
pkts.push_back(new Packet("10:20", "251"));
pkts.push_back(new Packet("10:20", "1006"));
// remove packet from vector if time and ID are the same
return 0;
}
Thanks
Two options to be able to use std::unique:
Define an operator== method for Packet and change the vector<Packet*> to vector<Packet>.
bool Packet::operator==(const Packet& rhs) const
{
if (getFilingTime() != rhs.getFilingTime())
return false;
if (getSpid() != rhs.getSpid())
return false;
return true;
}
//etc.
int main()
{
vector<Packet> pkts;
pkts.push_back(Packet("10:20", "1004"));
pkts.push_back(Packet("10:20", "1004")); // not unique (duplicate of the line above)
pkts.push_back(Packet("10:20", "251"));
pkts.push_back(Packet("10:20", "1006"));
// remove packet from vector if time and ID are the same
pkts.erase(unique(pkts.begin(), pkts.end()), pkts.end());
return 0;
}
Keep the vector as vector<Packet*> and define a method to compare the elements.
bool comparePacketPtrs(Packet* lhs, Packet* rhs)
{
if (lhs->getFilingTime() != rhs->getFilingTime())
return false;
if (lhs->getSpid() != rhs->getSpid())
return false;
return true;
}
//etc.
int main()
{
vector<Packet*> pkts;
pkts.push_back(new Packet("10:20", "1004"));
pkts.push_back(new Packet("10:20", "1004")); // not unique (duplicate of the line above)
pkts.push_back(new Packet("10:20", "251"));
pkts.push_back(new Packet("10:20", "1006"));
// remove packet from vector if time and ID are the same
pkts.erase(unique(pkts.begin(), pkts.end(), comparePacketPtrs), pkts.end());
return 0;
}
As an alternative to unique, you could simply insert the elements into a set (or unordered_set in C++11).
Whichever way you decide to go, you'll need to define comparison operators for Packet. For unique, you'll need operator==; for set you'll need operator<. For completeness, you should define both, and their counterparts:
class Packet {
…
bool operator==(const Packet& p) const {
return fillingTime == p.fillingTime && recordID == p.recordID;
}
bool operator<(const Packet& p) const {
return fillingTime < p.fillingTime ||
(fillingTime == p.fillingTime && recordID < p.recordID);
}
bool operator!=(const Packet& p) const { return !(*this == p); }
bool operator> (const Packet& p) const { return p < *this; }
bool operator>=(const Packet& p) const { return !(*this < p); }
bool operator<=(const Packet& p) const { return !(p < *this); }
…
};
If you use C++11's unordered_set, you'll need to go one step further and define a hash function.
EDIT: I just noticed you're storing pointers to Packet. Why? Just store Packet directly.