std::sort vector of struct invalid operator< - c++

I have problem with strict weak ordering in the compare function in std::sort. I can't see why this would fail.
I have some nested structs:
struct date{
int day = 1;
int month = 1;
int year = 2017;
};
struct hhmmss{
int hours = 1;
int minutes = 1;
int seconds = 1;
};
struct dateAndTime {
date d;
hhmmss t;
};
struct Trade
{
/*
other unrelevant data
*/
dateAndTime timeClosed;
};
In my code, at some point I have a populated std::vector<Trade> which I want to sort.
My sort function:
void sortTradesByDate(std::vector<Trade>& trades){
std::sort(trades.begin(), trades.end(), compareDateAndTime);
}
My comparison function:
bool compareDateAndTime(const Trade& t1, const Trade& t2){
if (t1.timeClosed.d.year < t2.timeClosed.d.year)
return true;
else if (t1.timeClosed.d.month < t2.timeClosed.d.month)
return true;
else if (t1.timeClosed.d.day < t2.timeClosed.d.day)
return true;
else if (t1.timeClosed.t.hours < t2.timeClosed.t.hours)
return true;
else if (t1.timeClosed.t.minutes < t2.timeClosed.t.minutes)
return true;
else if (t1.timeClosed.t.seconds < t2.timeClosed.t.seconds)
return true;
return false;
}
When running the function, and debuging, my first item passed to compareDateAndTime() passes after returning true on one of the statements (months).
The next item returns true at hours comparison, but then I get a "Debug Assertion Failed!" with "Expression: invalid operator<".
Doing some googling, this has to do with strict weak ordering. But why does this fail when comparing int variables?

Your comparison function isn't implementing strict weak ordering
Consider this scenario:
t1: year=2017, month=2
t2: year=2016, month=5
compareDateAndTime(t1, t2) would return true.
You should proceed to compare month if and only if year is the same.
if (t1.timeClosed.d.year < t2.timeClosed.d.year)
return true;
if (t1.timeClosed.d.year > t2.timeClosed.d.year)
return false;
if (t1.timeClosed.d.month < t2.timeClosed.d.month)
return true;
if (t1.timeClosed.d.month > t2.timeClosed.d.month)
return false;
... and so forth ...

A nice way to leverage the Standard Library:
return std::tie(t1.timeClosed.d.year, t1.timeClosed.d.month) < std::tie(t2.timeClosed.d.year, t2.timeClosed.d.month);
You can add the missing members inside the std::tie's (it's a variadic template). This uses std::tuple's operator<, which is defined to do what you expect.

Related

C++ recursive struct comparator

I have created a struct to use as a key in a map to avoid having duplicate elements.
The struct contains pointers to children and siblings of its own type.
For the map, I have created a custom comparator that is supposed to recursively look at the element, the children and the siblings until a difference is found to make sure the elements are the same.
However, for some reason it is not working and Im still getting duplicates. After checking them out in the debugger, I concluded that they are indeed the exact same through and through so the problem must probably be somewhere in there.
This is the struct.
struct controlIdentifier
{
DWORD m_dwID;
DWORD m_dwDefaultID;
DWORD m_dwDisableID;
BYTE m_bType;
int m_nWidth;
int m_nHeight;
int m_nMargineH;
int m_nMargineV;
shared_ptr<controlIdentifier> m_pCHILD;
shared_ptr<controlIdentifier> m_pNEXT;
bool operator<(const controlIdentifier& id) const
{
if (m_dwDefaultID < id.m_dwDefaultID)
return true;
if (m_dwDisableID < id.m_dwDisableID)
return true;
if (m_bType < id.m_bType)
return true;
if (m_nWidth < id.m_nWidth)
return true;
if (m_nHeight < id.m_nHeight)
return true;
if (m_nMargineH < id.m_nMargineH)
return true;
if (m_nMargineV < id.m_nMargineV)
return true;
if (!m_pCHILD && id.m_pCHILD)
return true;
if (m_pCHILD && !id.m_pCHILD)
return false;
if (!m_pNEXT && id.m_pNEXT)
return true;
if (m_pNEXT && !id.m_pNEXT)
return false;
bool smaller = false;
if (m_pCHILD && id.m_pCHILD)
smaller = *m_pCHILD < *id.m_pCHILD;
if (!smaller)
{
if (m_pNEXT && id.m_pNEXT)
return *m_pNEXT < *id.m_pNEXT;
}
else
return smaller;
return false;
}
};
And this is how it's used.
struct cmpBySharedPtr {
bool operator()(const shared_ptr<controlIdentifier>& a, const shared_ptr<controlIdentifier>& b) const {
return *a < *b;
}
};
std::set<FRAMEDESC_SHAREDPTR> m_curFrames;
std::map<shared_ptr<controlIdentifier>, FRAMEDESC_SHAREDPTR, cmpBySharedPtr> m_serialFrames;
for (auto&& frame : m_curFrames)
{
shared_ptr<controlIdentifier> id;
makeIdentifiers(frame, id);
id->m_dwID = newId;
auto find = m_serialFrames.find(id);
if (find == m_serialFrames.end())
{
m_serialFrames.insert(std::pair(id, frame));
newId++;
}
}
m_dwID is not being compared on purspose.
Consider A = (child = 5, next = 6) and B = (child = 6, next = 5). Now A<B is true as (A.child < B.child) is true and it just returns that. Now consider B<A. B.child < A.child is false, so it checks the next fields.. Now B.next < A.next is true, so your comparison returns true.
So this is nonsensical -> A<B is true and B<A is true. This means your comparator is invalid.
The technical term for this is the comparator requires strict weak ordering - see https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. Your comparator breaks the asymmetry requirement.
You can construct operator < by comparing field by field. But what you did is too little. Basically it shall look like this:
bool operator < (const A& left, const A& right)
{
if (left.firstField < right.firstField) return true;
if (right.firstField < left.firstField) return false; // this case is missing
if (left.secondField < right.secondField) return true;
if (right.secondField < left.secondField) return false; // this case is missing
....
return false;
}
You are missing cases when you can conclude, that for sure, left object is "greater" than right object.

Sorting a Vector of a custom class with std::sort() causes a segmentation fault

I'd like to sort a vector of a custom class using std::sort() and overloading the < operator. Following the answer here: Sorting a vector of custom objects, I tried the following code:
class Evnt {
private:
int Day, Month;
string mydata;
public:
friend ifstream& operator >>(ifstream &in,Evnt &E){
char junk;
in >>junk>>E.Month>>junk>>E.Day;
getline(in,E.mydata);
return in;
}
bool operator<(const Evnt &E) const{
if(Month < E.Month)
return true;
else if(Day < E.Day)
return true;
return false;
}
};
int main(){
ifstream inpt("inputfile.txt")
Vector <Evnt> v;
Evnt tmpevnt;
while(intp>>tmpevnt)
v.push_back(tmpevent)
sort(v.begin(), v.end());
return 0;
}
The last line somewhat erratically causes segmentation faults. I followed the various examples fairly closely, and so am having issues figuring out what the problem is. It seems to only occur if I read a large number (~20+) items in.
std::sort requires a comparison operation that imposes Strict Weak Ordering.
That means that if a < b returns true, b < a must not return true.
Fix your comparison operator.
bool operator<(const Evnt &E) const{
if(Month < E.Month)
return true;
else if(Month == E.Month && Day < E.Day)
return true;
return false;
}
See this similar question for more information.

How to represent a mathematical domain in IR?

I would like to define an object representing a mathematical domain from a list of constraints, but I don't have a clear idea on how to do that.
For example, I start from IR and I have the following constraints :
x > 0
x is not in ]3,5]
x is not in [7,12[
Then, my domain is ]0,3] U ]5,7[ U [12,+oo .
How can I nicely store that in a C++ structure ? Have you ever did that before ? Moreover, I want to be able to check easilly if the domain is empty.
Unless you want to use "3rd party" tools like mentioned in the coments, you'll have to write your own Interval class.
To do this, you can do something like this:
class Interval{
struct Range{
bool leftInclusive, rightInclusive;
double left, right;
bool operator<(Range other){return left<other.left;}
}
std::Set<Range> trueRanges;
void addTrueRange(Range r){
//check for overlaps
//merge if overlapping
//otherwise add to trueRanges
}
bool trueAt(double at){
//find the range with the highest left-bound lower than at
auto candidate = truethRanges.upper_bound(at);
if(candidate == trueRanged.end()) return false; // no range found
//on-point checking here
if(at <= candidate->left) return false;
if(at >= candidate->right) return false;
return true;
}
}
The on-point checking is left out here because you cannot simply say doubleOne == doubleTwo because this mitght result in false negatives. So you have to say ABS(doubleOne-doubleTwo) < tinyValue.
For looking for overlaps you can have a look at this.
Answering my own question.
Actually, I followed the idea of sbabbi using a list of intervals coming from boost/numeric/interval, representing the union of intervals.
Here is an example :
typedef boost::numeric::interval_lib::rounded_math<double> RoundedPolicy;
typedef boost::numeric::interval_lib::checking_base<double> CheckingPolicy;
typedef boost::numeric::interval_lib::policies<RoundedPolicy,CheckingPolicy> IntervalPolicies;
typedef boost::numeric::interval<double,IntervalPolicies> interval;
//...
bool is_interval_empty(const interval& inter)
{
return boost::numeric::empty(inter);
}
void restrict(interval& domain, const interval& inter)
{
for(std::list<interval>::iterator it = domain.begin(); it != domain.end(); ++it)
*it = boost::numeric::intersect(*it, inter);
domain.remove_if(is_interval_empty);
}
void restrict(interval& domain, const interval& inter1, const interval& inter2)
{
for(std::list<interval>::iterator it = domain.begin(); it != domain.end(); ++it)
{
domain.push_front(boost::numeric::intersect(*it, inter1));
*it = boost::numeric::intersect(*it, inter2);
}
domain.remove_if(is_interval_empty);
}
//...
std::list<interval> domain;
for(unsigned long int i = 0; i < constraints.size(); ++i)
{
if(constraints[i].is_lower_bound())
{
interval restriction(constraints[i].get_lower_bound(), std::numeric_limits<double>::infinity());
restrict(domain, restriction);
}
else if(constraints[i].is_upper_bound())
{
interval restriction(-std::numeric_limits<double>::infinity(), constraints[i].get_upper_bound());
restrict(domain, restriction);
}
else if(constraints[i].is_forbidden_range())
{
interval restriction1(-std::numeric_limits<double>::infinity(), constraints[i].get_lower_bound());
interval restriction2(constraints[i].get_upper_bound(), std::numeric_limits<double>::infinity());
restrict(domain, restriction1, restriction2);
}
}
if(domain.size() == 0)
std::cout << "empty domain" << std::endl;
else
std::cout << "the domain exists" << std::endl;

Searching first specified element in array

I'm trying to make function that has a loop that checks every member of an array made from boolean variables and exits when it finds the first "true" value.
That's what I have now:
bool solids[50];
int a,i;
//"equality" is a function that checks the equality between "a" and a defined value
solids[0] = equality(a,&value_1);
solids[1] = equality(a,&value_1);
solids[2] = equality(a,&value_1);
solids[3] = equality(a,&value_1);
for (i = 0; solids[i] != true; i++)
{
[...]
}
But I have no idea, what should I put into the loop?
My attempt was
for (i = 0; i <= 50; i++)
{
if (solids[i] == true)
{
return true;
break;
} else {
return false;
}
}
,that should return true after the first found true and return false if the array has no member with true value, but it doesn't seem to work in the code.
Is it wrong? If yes, what is the problem?
PS.: I may count the number of trues with a counter but that's not an optimal solve to the problem, since I just look for the FIRST true value and consequently, the program doesn't have to check all the 50 members. Needley to count, how many unnecesary steps should this solve would mean.
here's a short example usage of std::find() as advised by #chris:
bool find_element_in_array() {
bool solids[50];
int length;
/* ... do many operations, and keep length as the size of values inserted in solids */
bool* location = std::find(solids, length, true);
// if element is found return true
if (location != solids + length)
return true;
// else return false
return false;
}
Once you have solids correctly set (it looks like you're currently setting every value to the same thing), you can make a loop that exits on the first true like this:
for (i = 0; i < 50; i++)
{
if (solids[i] == true)
{
return true;
}
}
return false;
I'd also just move the declaration of i into the for loop body, since it's not used outside, but the above answers your question.
return immediately exits the function, so there is no need to break the loop after.
If it's sufficient to exit the function right after the search, you should write something like:
for (int i = 0; i < 50; i++) {
if (solids[i]) return true;
}
return false;
If you need to use the result of the search in the same function, use additional variable:
bool found = false;
for (int = 0; i < 50; i++) {
if (solids[i]) {
bool = true;
break;
}
}
if (found) { ...

Error in function sort

I'm trying to use the sort function from STL, but it gives me an error during execution.
My compare function returns true if v is smaller then e:
bool smallerThan(VertexEntry &v, VertexEntry &e) {
if(v.v[0] < e.v[0]) return true;
else if(v.v[1] < e.v[1]) return true;
else if(v.v[2] < e.v[2]) return true;
return false;
}
and here is the call:
sort(vertices.begin(),vertices.end(),smallerThan);
The size of the vector is aprox 400 elements.
Can somebody help me solve my problem?
Thank you!!
Your comparison function is incorrect - it doesn't enforce strict weak ordering.
Use this:
bool smallerThan(VertexEntry const & v, VertexEntry const & e) {
if (v.v[0] < e.v[0])
return true;
else if(v.v[0] > e.v[0])
return false;
else if(v.v[1] < e.v[1])
return true;
else if(v.v[1] > e.v[1])
return false;
else if(v.v[2] < e.v[2])
return true;
return false;
}
Your comparison operator doesn't enforce strict weak ordering. If you're able to use boost one trick I've seen is to bind your object to a boost::tuple and use its strict weak operator<.
If you need to write it yourself, something like this should work:
bool smallerThan(const VertexEntry &v, const VertexEntry &e)
{
if(v.v[0] != e.v[0]) return v.v[0] < e.v[0];
else if(v.v[1] != e.v[1]) return v.v[1] != e.v[1];
else return v.v[2] < e.v[2];
}