您的位置:首页 > 编程语言 > C语言/C++

[Tech Note] Comparison functions in C++

2014-11-12 22:46 253 查看

Introduction

Prior to using STL functions such as sort(), you need to define precisely the behaviour for comparing two objects of the same type, i.e. define the comparison function to supprt ordering. All of comparison sort algorithms (quick sort, bubble sort, merge
sort, heap sort etc.) will compare two objects based on the user-defined or default comparison functions. This article is to show you the basic principles of a comparison function and how to define it in C++ especially for classes or structs.

Typically, a comparison function is a binary predicate that compares two objects, returning true if the first precedes the second. This predicate must satisfy the standard mathematical definition of a strict weak ordering.

Strict weak orderings

Let cmp_pred(x,y) be our comparison function where x and
y are two underlying objects of any type to compare. cmp_pred follows strict weak ordering if it has the following properties. More descriptions are

Wiki,
SGI.

Irreflexivity. For all x, it is not the case that x < x. i.e.
cmp_pred(x,x) yeilds false
Asymmetry. For all x, y, if x < y then it is not the case that y < x. i.e.
if cmp_pred(x,y) yeilds true, then cmp_pred(y,x) must yeild false
Transitivity. For all x, y, and z, if x < y and y < z then x < z. i.e. if
cmp_pred(x,y) and cmp_pred(y,z) both yield true, then cmp_pred(x,z) will yield true
Transitivity of incomparability. For all x, y, and z, if x is incomparable with y, and y is incomparable with z, then x is incomparable with z. i.e.
if cmp_pred(x,y), cmp_pred(y,x), cmp_pred(y,z) and cmp_pred(z,y) all yield false, then cmp_pred(x,z) and cmp_pred(z,x) will both yield false. Here incomparability refers to both
cmp_pred(x,y) and cmp_pred(y,x) yield false, say cmp_pred is defiend as "smaller than".

This list of properties is somewhat redundant, as asymmetry follows readily from irreflexivity and transitivity. The first three should be simple enough. The last one is a bit odd. The basic idea behind th elast one is that the relation doesn't entirely
order element but groups them into equivalent classes. If you think of the relation cmp_pred to be "smaller than", it just says that if neither x is smaller than y nor y is smaller than x, then x and y are equivalent. The incomparable elements are
just equivalent. In STL, two objects x and y are equivalent if and only if cmp_pred(x,y) and
cmp_pred(y,x) both yield false.

How to define ordering

The class object that the ordering works upon is defined as below.

class Person
{
public:
string FirstName;
string SecondName;
Person(string firstName,string secondName):FirstName(firstName),SecondName(secondName){}
friend inline ostream& operator << (ostream & os,const Person& person)
{
os << person.SecondName << ", " << person.FirstName;
return os;
}
};


Given the current definition of Person, if we execute the following codes, the compiler will throw errors as Person does not explicitly define the relation between its instances.

vector<Person> personList;
personList.push_back(Person("Tom","Green"));
personList.push_back(Person("Meimei","Han"));
personList.push_back(Person("Jim","Green"));
personList.push_back(Person("Leilei","Li"));
personList.push_back(Person("Tim","Green"));

sort(personList.begin(),personList.end());

for(vector<Person>::iterator iter = personList.begin() ; iter != personList.end() ; ++iter)
cout<<*iter<<endl;

Here we introduce three approaches to define an ordering (referencing
here). The desired relation is very straightforward. Two Person instances will be compared according to their second names, or if they are equal, compared according to their first names, i.e. in alphabetically ascending order. Consequently, the output should
be as:



Way 1. Overload operator <()

This method can be used if you want objects of a custom class to be able to be sorted naturally. By overloading "less than" (<) operator in either of the two following ways, you can define the natural relationship between the objects as expected.
Be sure to return false at the end to garantee Irreflexivity.

Syntax One:

friend bool operator < (const Person& first, const Person& second)
{
if(first.SecondName != second.SecondName)
return first.SecondName < second.SecondName;
if(first.FirstName != second.FirstName)
return first.FirstName < second.FirstName;
return false;
}

Syntax Two:

bool operator<(const Person& other) const
{
if(SecondName != other.SecondName)
return SecondName < other.SecondName;
if(FirstName != other.FirstName)
return FirstName < other.FirstName;
return false;
}


Way 2. Custom comparison function

This method is used especially when you are comparing built-in types, you cannot modify the class you are comparing, or you want to define another ordering besides its natural ordering. The custom comparison function will be explicitly passed to STL functions
as one argument.

Basically in our case, a comparison function is just a function that takes two Person as the parameters and returns a boolean indicating their relation. The prototype is:

bool func_name(const T& first, const T& second)

We can define such comparison function outside the class definition.

bool cmp_func(const Person& first, const Person& second)
{
if(first.SecondName != second.SecondName)
return first.SecondName < second.SecondName;
if(first.FirstName != second.FirstName)
return first.FirstName < second.FirstName;
return false;
}
Pass it to sort() as the third argument.

sort(personList.begin(),personList.end(),cmp_func);


Way 3. Define operator ()()

For STL container set, you can define the ordering through

set<int, bool (*)(Person, Person)> s(cmp_func);

Instead, you can use a functor, which is an object that can behave like a function. This is done by defining operator()() of the class. In this case, implement operator()() as a comparison function:

struct CmpFunctor
{
bool operator()(const Person& first, const Person& second)
{
if(first.SecondName != second.SecondName)
return first.SecondName < second.SecondName;
if(first.FirstName != second.FirstName)
return first.FirstName < second.FirstName;
return false;
}
};

Then you can pass this class as an argument to STL containers or functions

For set:

set<Person,CmpFunctor> personSet;

A functor can be used as an ordinary function by instantiating the class. For sort():
sort(personList.begin(),personList.end(),CmpFunctor());

In fact, STL has provided with many functors, such as greater<T>, less<T>, equal_to<T>, etc.


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: