I frequently encounter situations, especially with sorting in C++, where I am comparing a series of fields in order to compare a larger structure. A simplified example:
struct Car{
Manufacturer make;
ModelName model;
Year year;
};
bool carLessThanComparator( const Car & car1, const Car & car2 ){
if( car1.make < car2.make ){
return true;
}else if( car1.make == car2.make ){
if( car1.model < car2.model ){
return true;
}else if( car1.model == car2.model ){
if( car1.year < car2.year ){
return true;
}
}
}
return false;
}
My instinctive approach seems cumbersome, especially for more than 3 fields. How would you structure this series of comparisons in C++? Do other languages provide a more succinct or elegant syntax?
Well, if your function hits a return in the if clause, there's no need for an explicit else, since it would have already bailed out. That can save on the "indent valley":
bool carLessThanComparator( const Car & car1, const Car & car2 ) {
if( car1.make < car2.make )
return true;
if ( car1.make != car2.make )
return false;
if( car1.model < car2.model )
return true;
if( car1.model != car2.model )
return false;
if( car1.year < car2.year )
return true;
return false;
}
I like MarkusQ's LISPish short-circuiting approach as well.
If this happens a lot you could put a template like this into a common header:
template<typename T, typename A1, typename A2, typename A3>
bool
do_less_than(
const typename T& t1,
const typename T& t2,
const typename A1 typename T::* a1,
const typename A2 typename T::* a2,
const typename A3 typename T::* a3)
{
if ((t1.*a1) < (t2.*a1)) return true;
if ((t1.*a1) != (t2.*a1)) return false;
if ((t1.*a2) < (t2.*a2)) return true;
if ((t1.*a2) != (t2.*a2)) return false;
return (t1.*a3) < (t2.*a3);
}
Add other templates for different numbers of arguments as required. For each less than function, you can then do something like this:
bool carLessThanComparator(const Car& car1, const Car& car2)
{
return do_less_than(car1, car2, &Car::make, &Car::model, &Car::year);
}
Personally I'd suggest NOT using the != or == operators like we seem to recommend here - this requires the arguments/members to have both less then and equal operators just to do a less then check on a class containing them - using just the less then operator is enought and will save you redundancy and potential defects in the future.
I suggest you write:
bool operator<(const Car &car1, const Car &car2)
{
if(car1.make < car2.make)
return true;
if(car2.make < car1.make)
return false;
if(car1.model < car2.model)
return true;
if(car2.model < car1.model)
return false;
return car1.year < car2.year;
}
I know it's an old question, but for future visitors: the modern C++11 solution is to use std::tie
struct Car{
Manufacturer make;
ModelName model;
Year year;
};
bool operator<(Car const& lhs, Car const& rhs)
{
return std::tie(lhs.make, lhs.model, lhs.year) < std::tie(rhs.make, rhs.model, rhs.year);
}
std::tie converts the struct into a std::tuple so that the above comparison operator delegates to std::tuple::operator<. This in turn does a lexicographical compare with respect to the order in which the members are marshalled into std::tie.
The lexicographic comparison is short-circuited in the same way as in the other solutions to this question. But it is even succinct enough to define on the fly inside a C++ lambda expression. For classes with private data members, it's best defined inside the class as friend function.
bool carLessThanComparator( const Car & car1, const Car & car2 ){
return (
( car1.make < car2.make ) or (( car1.make == car2.make ) and
( car1.model < car2.model ) or (( car1.model == car2.model ) and
( car1.year < car2.year )
)));
-- MarkusQ
Personally, I'd override the ==, <, >, and any other operators needed. That would clean up the code, not in the comparison, but when you need to make the comparison.
For the actual comparison itself, I would write it similarly to what Crashworks said.
bool operator<(const Car &car1, const Car &car2) {
if(car1.make < car2.make)
return true;
if(car1.make != car2.make)
return false;
if(car1.model < car2.model)
return true;
if(car1.model != car2.model)
return false;
return car1.year < car2.year;
}
I was wondering the same thing as the OP and stumbled upon this question. After reading the answers I have been inspired by janm and RnR to write a lexicographicalMemberCompare template function that only uses only operator< on the compared members. It also uses boost::tuple so that you can specify as many members as you want. Here it is:
#include <iostream>
#include <string>
#include <boost/tuple/tuple.hpp>
template <class T, class Cons>
struct LessThan
{
static bool compare(const T& lhs, const T& rhs, const Cons& cons)
{
typedef LessThan<T, typename Cons::tail_type> NextLessThan;
typename Cons::head_type memberPtr = cons.get_head();
return lhs.*memberPtr < rhs.*memberPtr ?
true :
(rhs.*memberPtr < lhs.*memberPtr ?
false :
NextLessThan::compare(lhs, rhs, cons.get_tail()));
}
};
template <class T>
struct LessThan<T, class boost::tuples::null_type>
{
static bool compare(const T& lhs, const T& rhs,
const boost::tuples::null_type& cons)
{
return false;
}
};
template <class T, class Tuple>
bool lexicographicalMemberCompare(const T& lhs, const T& rhs,
const Tuple& tuple)
{
return LessThan<T, typename Tuple::inherited>::compare(lhs, rhs, tuple);
}
struct Car
{
std::string make;
std::string model;
int year;
};
bool carLessThanCompare(const Car& lhs, const Car& rhs)
{
return lexicographicalMemberCompare(lhs, rhs,
boost::tuples::make_tuple(&Car::make, &Car::model, &Car::year));
}
int main()
{
Car car1 = {"Ford", "F150", 2009};
Car car2 = {"Ford", "Escort", 2009};
std::cout << carLessThanCompare(car1, car2) << std::endl;
std::cout << carLessThanCompare(car2, car1) << std::endl;
return 0;
}
Hope this is useful to someone.
Related
I'm trying to write a custom comparator for a C++ map which has a custom defined key.
struct key { int year; int no; };
map<key, detail, compare> details_map;
if the year values are equal, it must compare the no values.
I'm trying to figure out a way to write a comparator that can compare both values. So far, I am only able to write a comparator which compares one value.
struct Compare{bool operator()(const key &lhs,const key &rhs)const{return lhs.year<rhs.year;}}
Can someone please explain how a comparator works in a map?
Also, is it possible to write the comparator as a function?
Inside your operator(), simply compare the no values if the year values are equal:
struct Compare {
bool operator()(const key &lhs, const key &rhs) const {
if (lhs.year == rhs.year) {
return lhs.no < rhs.no;
}
return lhs.year < rhs.year;
}
};
And yes, a comparator can be implemented as a standalone function instead:
bool Compare (const key &lhs, const key &rhs) {
if (lhs.year == rhs.year) {
return lhs.no < rhs.no;
}
return lhs.year < rhs.year;
}
Alternatively, you can have your comparator use std::tie() to compare your key fields. See #Jarod42's answer.
Though, it would make more sense to implement operator< for your key type instead:
struct key {
int year;
int no;
bool operator<(const key &rhs) const {
if (year == rhs.year) {
return no < rhs.no;
}
return year < rhs.year;
}
};
Or
struct key {
int year;
int no;
};
bool operator<(const key &lhs, const key &rhs) {
if (lhs.year == rhs.year) {
return lhs.no < rhs.no;
}
return lhs.year < rhs.year;
}
Then you don't need a separate comparator:
map<key, detail> details_map;
std::tie allows simple lexicographical comparison:
struct Compare {
bool operator()(const key& lhs, const key& rhs) const {
return std::tie(lhs.year, lhs.no) < std::tie(rhs.year, rhs.no);
}
};
Method/function as_tuple might be interesting to avoid some repetitions:
struct key { int year; int no; };
auto as_tuple(const key& k) { return std::tie(k.year, k.no); }
struct Compare {
bool operator()(const key& lhs, const key& rhs) const {
return as_tuple(lhs) < as_tuple(rhs);
}
};
I have a set, and for this set, I need two different comparators. For example, for a set frontier I need to sort by cost, but I have another set board which needs to be sorted by coordinates. I know you can define a comparator for each set using the comparator as the second argument, but I have tried this and it gave me an error.
The code I tried to use:
struct tile {
int id;
int xCord;
int yCord;
int cost;
...
bool operator<(const tile& Rhs) const {
if (cost < Rhs.cost) {
return true;
}
else if (cost < Rhs.cost) {
return false;
}
else {
if (id < Rhs.id) {
return true;
}
else
return false;
}
}
...
};
The other struct that I'm using for the comparator (I know this is most likely incorrect, which is why I'm asking for help.):
struct costComp {
int id;
int xCord;
int yCord;
int cost;
costComp() {}
costComp(int a, int b, int c, int d = 0) :
id(a),
xCord(b),
yCord(c),
cost(d) {}
bool operator<( const tile& Rhs) const {
if (xCord < Rhs.xCord)
return true;
else if (xCord < Rhs.xCord)
return false;
else {
if (yCord < Rhs.yCord)
return true;
else if (yCord < Rhs.yCord)
return false;
else
return false;
}
}
};
Then, I define the set as:
set<tile,costComp> startBoard;
The error I got:
c2064: term does not evaluate to a function taking 2 arguments
Any help is greatly appreciated.
the Compare parameter in std::set is intended to be some callable type that can be invoked with (const tile&, const tile&). This means you can use a functor that overloads operator(), for example, like this:
struct Comp {
bool operator()(const tile& lhs, const tile& rhs) const {
if (lhs.id < rhs.id) return true;
if (lhs.id > rhs.id) return false;
if (lhs.xCord < rhs.xCord) return true;
if (lhs.xCord > rhs.xCord) return false;
if (lhs.yCord < rhs.yCord) return true;
if (lhs.yCord > rhs.yCord) return false;
return lhs.cost < rhs.cost;
}
// or maybe, if this logic already exists:
bool operator()(const tile& lhs, const tile& rhs) const {
return lhs < rhs; // invoke tile::operator<(const tile&)
}
};
...
std::set<tile, Comp> myset;
This way, the comparator struct doesn't need to keep track of the details of any one tile object, and the redundant members of costComp can be removed.
If you want the comparator to be configurable, you can add members to the Comp struct definition and initialize them in a constructor call when you instantiate the set:
struct Comp {
Comp(bool use_cost = false /* default behavior */) : m_use_cost(use_cost) {}
bool operator()(const tile& lhs, const tile& rhs) const {
if (m_use_cost){
return lhs.cost < rhs.cost;
} else {
...
}
}
private:
const bool m_use_cost;
};
...
// default comparison, won't use cost
std::set<tile, Comp> setA;
// specify custom behaviour
std::set<tile, Comp> setB {Comp{true /* right here */}};
Obviously, the configurability is not limited to one or more bools. It might make sense to have some enum with values like SortByCost, SortByXcoord. Alternatively, you could have a separate functor struct that does each, but this means that sets with different comparators will have different types and will not be inter-copyable or moveable.
i searched a lot here and on other sites as well but i have not found something satisfying.
what i need is quite simple task - substantially to construct ORDER BY operator in c++. this means i have struct with a number of various data type members and i need a comparator for it with members and orderings configurable. here is my pseudocode idea:
comparator.add(&MyStruct::member1, std::less);
comparator.add(&MyStruct::member2, std::greater);
std::sort(my_vector.begin(), my_vector.end(), comparator);
and i get data sorted by member1 and if it is equal member2 decides, and so on.
i am not too good in stl and templates, but i can read and decipher some code and found this as very appropriate solution: https://stackoverflow.com/a/11167563
unfortunately in my work i have to use c++ builder with faulty 32bit compiler that refuses to compile this correct code. it does support almost nothing from c++11, it has boost 1.39 available.
does someone have any solution that could work for me with my resources available? thank you in advance
EDIT:
i got very specialised solutions with hard-written comparison operators which i am aware of and which do not work here too good. i missed this in my question. my struct has at least 15 members and as i wrote, i need to often change individual sort directions for members/columns (asc, desc). too, i need to often change set of sorted members, just like in order by operator in sql, for example. also i cannot use something like stable_sort as i am just writing comparator for something like OnCompare event of some class.
It's not too difficult. First, consider the "canonical"
ordering relationship:
struct Compare
{
bool operator()( C const& lhs, C const& rhs ) const
{
return lhs.a < rhs.a
|| ( !(rhs.a < lhs.a) && lsh.b < rhs.b )
|| ( !(rhs.a < lhs.a) && !(rhs.b < lhs.b) && lhs.c < rhs .c )
|| ...
}
};
Obviously, no one would actually write something like this, but
it corresponds exactly to the formal definition of what is
needed.
Of course, if we can imagine the data members as an array, we
could rewrite this as a loop, taking advantage of the previously
established !(rhs[i-1] < lsh[i-1] in each case:
struct Compare
{
bool operator()( C const& lhs, C const& rhs ) const
{
int i = 0;
while ( i != N && !(lhs[i] < rhs[i]) && !(rhs[i] < lhs[i]) ) {
++ i;
}
return i != N && lhs[i] < rhs[i];
}
};
Or, if all of the elements are fully ordered, so that == is
also defined on them, and we can assume that it corresponds to
the equivalence relationship established by the weak partial
ordering:
struct Compare
{
bool operator()( C const& lhs, C const& rhs ) const
{
int i = 0;
while ( i != N && !(lhs[i] == rhs[i]) ) {
++ i;
}
return i != N && lhs[i] < rhs[i];
}
};
All that remains is to somehow translate this into something
that can process an arbitrary ordering of elements of arbitrary
types. There's an old saying that the solution to every problem
is an additional level of indirection, and it applies here.
First, we need some means of handling the different types of
each element. Polymorphism seems appropriate (although
templates could be made to work if the order in which the
elements were evaluated were fixed at compile time):
struct CompareOneElementOfC
{
virtual bool isLessThan( C const& lhs, C const& rhs) const = 0;
virtual bool isEqual( C const& lhs, C const& rhs) const = 0;
};
template <typename T, T C::*ptr>
struct ConcreteCompareOneElementOfC : public CompareOneElementOfC
{
virtual bool isLessThan( C const& lhs, C const& rhs) const
{
return lhs.*ptr < rhs.*ptr;
}
virtual bool isEqual( C const& lhs, C const& rhs) const
{
return lhs.*ptr == rhs.*ptr;
}
};
Depending on the types of the elements, you may need to hand
write specific concrete instances. And if any of the elements
doesn't support total ordering, you will have to omit the
isEqual, and modify the following code accordingly.
Having got this far, we need exactly one static instance of each
concrete Compare:
ConcreteCompareOneElementOfC<int, &C::a> const c1;
ConcreteCompareOneElementOfC<double, &C::b> const c2;
// ...
Finally, put the addresses of these instances in a table:
CompareOneElementOfC const* const cmp[] = { &c1, &c2 ... };
You can have different tables for different orderings. If there
are only a few, define static tables for each, and be done with
it. If the orderings can be arbitrary, create the table on the
fly before each sort, in the desired order.
Finally:
class Compare
{
CompareOneElementOfC const* const* begin;
CompareOneElementOfC const* const* end;
public:
template< size_t N >
Compare( CompareOneElementOfC const* const (&cmp)[N] )
: begin( cmp )
, end( cmp + N )
{
}
bool
operator()( C const& lhs, C const& rhs ) const
{
auto current = begin;
while ( current != end && (*current)->isEqual( lhs, rhs ) ) {
++ current;
}
return current != end && (*current)->isLessThan( lhs, rhs );
}
}
(Please note that I haven't actually tested this code, so there
are probably typos and other errors. Still, the basic idea
should be there.)
I think just overloading operator < will work for you.
struct Struct {
int member1;
int member2;
bool operator<(const Struct& rhs) const {
if (member1 != rhs.member1)
return member1 < rhs.member1
else
return member2 > rhs.member2
}
};
This way whenever any 2 instances of Struct are compared, they will be compared by the comparison function defined in operator <.
So a simple std::sort(vec.begin(), vec.end()) will just work!
EDIT:
Otherwise you can always define a functor which can be used to compare each element. This is just a class with an overloaded operator () which is used for comparison.
class ComparisonClass {
public:
bool operator()(const Struct& lhs, const Struct& rhs) {
if (lhs.member1 != rhs.member1)
return lhs.member1 < rhs.member1
else
return lhs.member2 > rhs.member2
}
};
You can additionally define some member values of the ComparisonClass which define the order of comparisons.
Using it would be calling it like so std::sort(vec.begin(), vec.end(), ComparisonClass());
EDIT2:
Slightly more elaborate code -
class ComparisonClass {
public:
bool operator()(const Struct& lhs, const Struct& rhs) {
for(int i=0; i<m_comparisonOrder.size(); i++) {
int pos = m_comparisonOrder[i];
if (lhs[pos] != rhs[pos]) {
if (m_comparisonType[pos])
return lhs[pos] < rhs[pos];
else
return lhs[pos] > rhs[pos];
}
}
}
std::vector<int> m_comparisonOrder.
std::vector<bool> m_comparisonType;
};
Here I'm assuming that Struct has an operator [] which returns the appropriate member variable.
Why not have a specialized comparator function which first checks member1 and if equal then checks member2?
Like
bool comparator(const MyStruct& s1, const MyStruct& s2)
{
if (s1.member1 == s2.member1)
return s1.member2 > s2.member2;
else
return s1.member1 < s2.member1;
}
Good afternoon, I have a C++ class Range which implements a operator < for use by std::multiset<Range> ranges_type.
Since the multiset constructor don't specify a a custom comparator functor, it uses the std::less operator <.
However, I need to use a second comparator functor for std::multiset ranges_type. Specifically, I would specify a second comparator:
std::multiset<Range, PointerCompare> where struct PointerCompare looks this :
struct PointerCompare{
bool operator()(const Range& a, const Range& b) const {
return (a.mPtr == b.mPtr)
}
Is it possible to use std:multiset with multiple comparator functions or is there a workaround? Thank you
The class Range looks this:
class Range {
public:
explicit Range(int item){
mLow = item;
mHigh = item;
mPtr = 0;
}
Range(int low, int high, char* ptr = 0,char* mapptr = 0){
mLow = low;
mHigh = high;
mPtr = ptr;
}
Range(void){
mLow = 0;
mHigh = 0;
mPtr = 0;
}
Range(const Range& r):
mLow(r.mLow),
mHigh(r.mHigh),
mPtr(r.mPtr)
{
}
bool operator==(const Range& rhs) const{
return (mLow <= rhs.mLow && mHigh >= rhs.mHigh);
}
bool operator<(const Range& rhs) const{
return mHigh < rhs.mHigh;
}
int low() const { return mLow; }
int high() const { return mHigh; }
char* getPtr() const { return mPtr; }
private:
int mLow;
int mHigh;
char* mPtr;
}; // class Range
Sounds almost like you'd be better if you used something from Boost::MultiIndex rather than trying to force several different comparator functions onto a std::multiset. They have a bunch of different container types (see here.) In particular I'd look at the ordered_indices versions.
I may have found a workaround for multiple comparator functions: Here it is:
Range targetRange = Range(PreviousNCopy,PreviousN, TmpPrevMapPtr);
bool Found = std::binary_search( ranges_type.begin(), ranges_type.end(),
targetRange, MyComparator() );
where: MyComparator is a struct :
struct MyComparator {
bool operator () ( const Range& d1, const Range& d2 ) const
{
return d1.getPtr() < d2.getPtr();
}
};
std::binary_search take o(log n) time but the std::multiset ranges_type must always remain sorted. Thank you.
I have a class with a few numeric fields such as:
class Class1 {
int a;
int b;
int c;
public:
// constructor and so on...
bool operator<(const Class1& other) const;
};
I need to use objects of this class as a key in an std::map. I therefore implement operator<. What is the simplest implementation of operator< to use here?
EDIT:
The meaning of < can be assumed so as to guarantee uniqueness as long as any of the fields are unequal.
EDIT 2:
A simplistic implementation:
bool Class1::operator<(const Class1& other) const {
if(a < other.a) return true;
if(a > other.a) return false;
if(b < other.b) return true;
if(b > other.b) return false;
if(c < other.c) return true;
if(c > other.c) return false;
return false;
}
The whole reason behind this post is just that I found the above implementation too verbose. There ought to be something simpler.
I assume you want to implement lexicographical ordering.
Prior to C++11:
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
bool Class1::operator<(const Class1& other) const
{
return boost::tie(a, b, c) < boost::tie(other.a, other.b, other.c);
}
Since C++11:
#include <tuple>
bool Class1::operator<(const Class1& other) const
{
return std::tie(a, b, c) < std::tie(other.a, other.b, other.c);
}
I think there is a misunderstanding on what map requires.
map does not require your class to have operator< defined. It requires a suitable comparison predicate to be passed, which conveniently defaults to std::less<Key> which uses operator< on the Key.
You should not implement operator< to fit your key in the map. You should implement it only if you to define it for this class: ie if it's meaningful.
You could perfectly define a predicate:
struct Compare: std::binary_function<Key,Key,bool>
{
bool operator()(const Key& lhs, const Key& rhs) const { ... }
};
And then:
typedef std::map<Key,Value,Compare> my_map_t;
It depends on if the ordering is important to you in any way. If not, you could just do this:
bool operator<(const Class1& other) const
{
if(a == other.a)
{
if(b == other.b)
{
return c < other.c;
}
else
{
return b < other.b;
}
}
else
{
return a < other.a;
}
}
A version which avoids multiple indentation is
bool operator<(const Class1& other) const
{
if(a != other.a)
{
return a < other.a;
}
if(b != other.b)
{
return b < other.b;
}
return c < other.c;
}
The "Edit 2" version of the author has on average more comparisons than this solution. (worst case 6 to worst case 3)
You could do:
return memcmp (this, &other, sizeof *this) < 0;
but that has quite a lot of of caveats - no vtbl for example and plenty more I'm sure.