I'm trying to use a std::set where I will throw a bunch of edges in, and have only the unique ones remain.
An Edge is a line between two (integer indexed) nodes. Edge (1,2)==(2,1), because these edges are undirected.
I'm encountering a puzzling situation though, with this. At the section marked //?? in the code below, the behavior is not as I expect.
The results of running this code are to only keep 2 edges, (1,2) and (4,8). (2,1) is discarded by the set, but it should not be unless I activate the commented out //|| ( A==o.B && B==o.A ) section in operator==! What is happening here?
This set<Edge> implementation is leaving me feeling .. edgy.
#include <stdio.h>
#include <set>
using namespace std ;
struct Edge
{
int A,B ;
Edge( int iA, int iB ) : A(iA), B(iB) {}
bool operator==( const Edge & o ) const {
//??
return ( A==o.A && B==o.B ) ;//|| ( A==o.B && B==o.A ) ;
}
bool operator<( const Edge& o ) const {//MUST BE CONST
return A < o.A && B < o.B ;
}
void print() const { printf( "( %d, %d )", A,B ) ; }
void compare( const Edge& o ) const {
print() ;
if( *this==o ) printf( "==" ) ;
else printf( "!=" ) ;
o.print() ;
puts("");
}
} ;
int main()
{
Edge e1( 1, 2 ) ;
Edge e2( 1, 2 ) ;
Edge e3( 2, 1 ) ;
Edge e4( 4, 8 ) ;
e1.compare( e2 ) ;
e1.compare( e3 ) ;
e1.compare( e4 ) ;
set<Edge> edges ;
edges.insert( e1 ) ;
edges.insert( e2 ) ;
edges.insert( e3 ) ;
edges.insert( e4 ) ;
printf( "%d edges\n", edges.size() ) ;
for( auto edge : edges )
{
edge.print();
}
}
C++ set does not care about your == operator as much as it does about your < operator. It is your < operator that presents the problem: if you would like to make sure that (1,2) is equal to (2,1), you should change the implementation of your < to behave like this:
bool operator<( const Edge& o ) const {
int myMin = min(A, B);
int myMax = max(A, B);
int hisMin = min(o.A, o.B);
int hisMax = max(o.A, o.B);
return myMin < hisMin || ( myMin == hisMin && myMax < hisMax );
}
What this implementation does is constructing a canonical representation of an edge, where the smaller of the {A,B} becomes the "canonical A", and the larger one becomes the "canonical B". When edges are compared in their canonical form, the equality of (1,2) and (2,1) can be implied from the fact that both (1,2) < (2,1) and (2,1) < (1,2) evaluate to false.
I believe your operator< is wrong, both e3<e2 and e2<e3 are false.
Maybe you wanted something like:
return A < o.A || ((A == o.A) && (B < o.B)) ;
I suggest that you change your Edge() constructor to ensure that A and B are always initialized such that A<=B (if edges can point back to their originating node) or A<B (if not), and forego having the extra logic in the operator== implementation. That seems less "edgy" to me.
Your comparison should be
return (A == o.A && B == o.B) || (B == o.A && A == o.B);
Related
Trying to implement a combination of 4 objects taken 2 at a time without taking into account the arrangement (such must be considered duplicates: so that order is not important) of objects with std::set container:
struct Combination {
int m;
int n;
Combination(const int m, const int n):m(m),n(n){}
};
const auto operator<(const auto & a, const auto & b) {
//explicitly "telling" that order should not matter:
if ( a.m == b.n && a.n == b.m ) return false;
//the case "a.m == b.m && a.n == b.n" will result in false here too:
return a.m == b.m ? a.n < b.n : a.m < b.m;
}
#include <set>
#include <iostream>
int main() {
std::set< Combination > c;
for ( short m = 0; m < 4; ++ m ) {
for ( short n = 0; n < 4; ++ n ) {
if ( n == m ) continue;
c.emplace( m, n );
} }
std::cout << c.size() << std::endl; //12 (but must be 6)
}
The expected set of combinations is 0 1, 0 2, 0 3, 1 2, 1 3, 2 3 which is 6 of those, but resulting c.size() == 12. Also, my operator<(Combination,Combination) does satisfy !comp(a, b) && !comp(b, a) means elements are equal requirement.
What am I missing?
Your code can't work1, because your operator< does not introduce a strict total ordering. One requirement for a strict total ordering is that, for any three elements a, b and c
a < b
and
b < c
imply that
a < c
(in a mathematical sense). Let's check that. If we take
Combination a(1, 3);
Combination b(1, 4);
Combination c(3, 1);
you see that
a < b => true
b < c => true
but
a < c => false
If you can't order the elements you can't use std::set. A std::unordered_set seems to more suited for the task. You just need a operator== to compare for equality, which is trivial and a hash function that returns the same value for elements that are considere identical. It could be as simple as adding m and n.
1 Well, maybe it could work, or not, or both, it's undefined behaviour.
Attached is the working code. The tricky part that you were missing was not adding a section of code to iterate through the already working set to then check the values. You were close! If you need a more thorough answer I will answer questions in the comments. Hope this helps!
#include <set>
#include <iostream>
using namespace std;
struct Combination {
int m;
int n;
Combination(const int m, const int n):m(m),n(n){}
};
const auto operator<(const auto & a, const auto & b) {
//explicitly "telling" that order should not matter:
if ( a.m == b.n && a.n == b.m ) return false;
//the case "a.m == b.m && a.n == b.n" will result in false here too:
return a.m == b.m ? a.n < b.n : a.m < b.m;
}
int main() {
set< Combination > c;
for ( short m = 0; m < 4; ++ m )
{
for ( short n = 0; n < 4; ++ n )
{
//Values are the same we do not add to the set
if(m == n){
continue;
}
else{
Combination s(n,m);
const bool is_in = c.find(s) != c.end();
if(is_in == true){
continue;
}
else{
cout << " M: " << m << " N: " << n << endl;
c.emplace( m, n);
}
}
}
}
cout << c.size() << endl; //16 (but must be 6)
}
I'm quite new to C++ (but know my way around C) so I'm probably missing something obvious.
TLDR: I use a std::set which stores elements twice, which is definitely not what I want.
Long story:
I've defined a class Clique and I need to store elements of this class in a set, so I've defined the < operator for Clique:
class Clique{
public :
int b;
int e;
int l;
std::set<int> X;
bool operator <( const Clique &rhs ) const
{
if( b < rhs.b)
return true;
if( e < rhs.e)
return true;
if( X.size() < rhs.X.size() )
return true;
std::set<int>::iterator itX = X.begin();
std::set<int>::iterator itrhs = rhs.X.begin();
// both sets have same size, need only to check end for one of them
while( (*itX == *itrhs) && ( itX != X.end() ) ){
++itX;
++itrhs;
}
if( itX == X.end() ){
//both sets are equal
return false;
}
else
return ( *itX < *itrhs );
}
void print_clique(FILE *F) const ;
};
(I wasn't sure how set comparison is done, so I wrote a routine for comparing them first by size, then element by element).
Now I want to store Clique elements in a set and this is where the problem appears.
My std::set
(1) does not appear to store Clique elements in the order I've defined;
(2) stores several copies of the same Clique
I've written a function to print a set of Clique:
void print_cliqueset(std::set<Clique> mySet){
int setsize = 0;
std::set<Clique>::iterator it = mySet.begin();
Clique cur_c = *it;
Clique prev_c = *it;
while( it != mySet.end() ){
// for( std::set<Clique>::iterator it = mySet.begin(); it != mySet.end(); ++it ){
it->print_clique(stdout);
setsize ++;
++it;
if( it != mySet.end() ){
cur_c = *it;
assert ( prev_c < cur_c);
gassert( prev_c.b <= cur_c.b );
prev_c = *it;
}
}
assert( setsize == mySet.size() );
}
My function is more complicated than needed but I wanted to make sure I understood what was going on.
Here is a typical output of printing such a set:
There's a line for each Clique, in which I print first b, then e, then the elements in the set X.
6829 9716 1 2 3 5 8 9 10
6792 9687 1 2 3 7 8 9 10
606 6531 1 2 3 5 6 7 8 9
6829 9687 1 2 3 5 7 8 9 10
410 9951 2 6
484 9805 1 2 4 6
494 9805 2 4 6 10
506 9805 1 2 5 6
484 9821 1 2 4
484 9871 2 3 4 6
506 9821 1 2 5
484 9802 1 2 3 4 6
486 9805 1 2 4 6 9
486 9802 1 2 3 4 6 9
507 9802 1 2 3 4 6 9 10
502 9802 1 2 3 4 6 10
506 9802 1 2 3 5 6
507 9806 1 2 4 9 10
507 9805 1 2 5 6 9
527 9806 1 2 5 9 10
As we can see, the cliques are not at all sorted on the order I defined (or wanted to define). They should be sorted first by member b (which is the first of each line), and this is not the case at all.
Then I have some duplicate lines in the output (not appearing in the example above but present in the full output). I guess the fact that I have duplicates is not surprising given that it seems confused about the order...
I guess the answer is something fairly obvious but I fail to see it. Any help would be appreciated!
Your bool operator <( const Clique &rhs ) const is wrong as it doesn't respect strict ordering.
It may simply be:
bool operator <(const Clique& rhs) const
{
return std::tie(b, e, X) < std::tie(rhs.b, rhs.e, rhs.X);
}
Your operator< is broken. Consider two Cliques:
c1 is {b = 0, e = 1, ...}
c2 is {b = 1, e = 0, ...}
Your code will return true for both c1 < c2 and c2 < c1.
Obviously, in such situation std::set shows strange behavior.
I would fix your operator< in the following way:
bool operator <( const Clique &rhs ) const
{
if( b != rhs.b)
return b < rhs.b;
if( e != rhs.e)
return e < rhs.e;
if( X.size() != rhs.X.size() )
return X.size() < rhs.X.size();
std::set<int>::iterator itX = X.begin();
std::set<int>::iterator itrhs = rhs.X.begin();
// both sets have same size, need only to check end for one of them
while((itX != X.end()) && (itX == *itrhs)){
++itX;
++itrhs;
}
if( itX == X.end() ){
//both sets are equal
return false;
}
else
return ( *itX < *itrhs );
}
The definition of operator< should be such that for each pair of elements 'b' and 'e' the relationship b < e should be used to determine any kind of relationship. The following equivalences are in force here:
a > b <==> b < a
a == b <==> !(a < b) && !(b < a)
a >= b <==> `!(a < b)
And so on. If you use multiple fields to be checked for every relationship check, then you have a kind-of multidimensional ranges. Making a flat range out of that can be only done this way:
More significant field is checked first; if in this field values aren't equal, you return the result immediately
Otherwise - if they are equal - you check the next field in the significance order and so on.
The requirement of using this complicated relationship definition in the set makes things actually harder for you because all you should do is to state whether one element is less than the other. So in your case you'll have to check for equality inside by yourself. Your procedure checks the fields "next in significance chain" also if lhs.b > rhs.b.
Operator < must provide strict weak ordering. I.e. if x < y then !(y < x) and !(y == x).
In the case of Clique, the requirements seem to be that the elements b, e, and X are compared lexographically.
The idiomatic way to represent this is to do all comparisons in terms of operator<:
#include <set>
class Clique{
public :
int b;
int e;
int l;
std::set<int> X;
bool operator <( const Clique &r ) const
{
auto const& l = *this;
if (l.b < r.b) return true;
if (r.b < l.b) return false;
if (l.e < r.e) return true;
if (r.e < l.e) return false;
if (l.X < r.X) return true;
if (r.X < l.X) return false;
return false;
}
void print_clique(FILE *F) const ;
};
And yes, std::set really does provide operator< when the key type provides it.
Another way to write this, as Jarod was alluding to is this:
#include <set>
#include <tuple>
class Clique{
public :
int b;
int e;
int l;
std::set<int> X;
bool operator <( const Clique &r ) const
{
auto const& l = *this;
return std::tie(l.b, l.e, l.X) < std::tie(r.b, r.e, r.X);
}
void print_clique(FILE *F) const ;
};
Which I think you'll agree is concise, expressive, correct and idiomatic.
I have following struct,
struct cube
{
int index l , b , h;
bool operator<(const cube & c2) const
{
if (l == c2.l && b == c2.b && h == c2.h)
return index < c2.index;
if (l == c2.l && b == c2.b)
return h < c2.h;
if (l == c2.l )
return b < c2.b;
return l < c2.l;
}
bool operator==(const cube c2)
{
return index != c2.index && l == c2.l && b == c2.b;
}
};
Now I want to apply upper_bound on vector of this struct as per condition in == operator.
However , it is still returning me those iterators where index are same
int pos2 = upper_bound(v.begin(),v.end(),v[i]) - v.begin();
i.e v[i].index is equal to v[pos2].index
It's possible that for two cube instances foo and bar that foo < bar is true when foo == bar is also true. You could fix that by writing index == c2.index && l == c2.l && b == c2.b as the returned expression in operator==.
This contradiction is the root cause of your issues, although note that std::upper_bound does itself only require that operator< is implemented appropriately; which yours is.
Isn't index more of a property of a collection of cubes rather than a given cube? That is, it shouldn't appear in the cube class?
How c++ set/map checks the equality of keys ?
for example in this example :
struct A
{
int id , val;
A( int _val = 0 , int _id = 0 )
{ val = _val , id = _id; }
bool friend operator < ( const A &x , const A &y )
{
return x.val < y.val;
}
};
set< A > s;
because we haven't written the == operator ?
it checks if (!(x < y) && !(y < x))
operator== is not used by std::set. Elements a and b are considered equal iff !(a < b) && !(b < a)
Note: A set is probably inappropriate if you define equality in a different sense than ordering. Equality in set essentially means the two element will have the same place in sorted sequence of items.
I am trying to use the std::set to contain a struct of three member variables.
struct blah{
int a,b,c;
bool operator < ( const blah& blo ) const{
return ( a < blo.a || (a == blo.a && (b != blo.b || c != blo.c ) ) );
}
};
But I keep getting an error that my operator < is invalid. What is wrong with my approach?
struct blah {
int a,b,c;
blah(int aa,int bb,int cc){ a=aa; b=bb; c=cc; }
bool operator < ( const blah& blo ) const{
return ( a < blo.a
|| (a == blo.a && b < blo.b )
|| (a == blo.a && b == blo.b && c < blo.c )
);
}
};
int main() {
std::set<blah> st;
st.insert(blah(1,2,3));
st.insert(blah(1,1,1));
st.insert(blah(1,3,2));
return 0;
}
After altering the code following #paxdiablo code, this worked well. Thanks y'all!
That code compiles fine for me in the following complete program:
#include <iostream>
struct blah {
int a,b,c;
bool operator < ( const blah& blo ) const{
return ( a < blo.a || (a == blo.a && (b != blo.b || c != blo.c ) ) );
}
};
int main (void) {
blah x, y;
x.a=2; x.b=2; x.c=2;
y.a=2; y.b=2; y.c=2;
if (x < y) std::cout << "x<y\n";
if (y < x) std::cout << "x>y\n";
if (!(y < x) && !(x < y)) std::cout << "x=y\n";
return 0;
}
Changing the fields of x and y outputs different messages.
But I see one major problem with the function. It can tell you that both x < y and y < x, in the situation where the two a fields are identical but the b fields differ between the two. If you set both a fields to 1 and set the b fields to 2 and 1, you see:
x<y
y<x
That's not going to end well :-)
The fact that what you're getting is a debug assertion (something specifically built to catch runtime errors in mostly debug code) leads me to believe that the runtime libraries may explicitly be checking for incorrect operator< overloads by detecting that latter case (ie, both x < y and y < x are true).
You should really fix that because it will cause all sorts of problems with collections where (for example) you need to keep things sorted.
By way of example, let's say you wanted to use a, b and c as keys in that priority. A function to do that would contain something like:
// Check primary key.
if (a < blo.a) return true;
if (a > blo.a) return false;
// Primary key equal here, use secondary key.
if (b < blo.b) return true;
if (b > blo.b) return false;
// Primary and secondary keys equal here, use tertiary key.
return (c < blo.c);