Why does STL work with a comparison function that is strict weak ordering? Why can't it be partial ordering?
A partial order would not be sufficient to implement some algorithms, such as a sorting algorithm. Since a partially ordered set does not necessarily define a relationship between all elements of the set, how would you sort a list of two items that do not have an order relationship within the partial order?
Simply, a strict weak ordering is defined as an ordering that defines a (computable) equivalence relation. The equivalence classes are ordered by the strict weak ordering: a strict weak ordering is a strict ordering on equivalence classes.
A partial ordering (that is not a strict weak ordering) does not define an equivalence relation, so any specification using the concept of "equivalent elements" is meaningless with a partial ordering that is not a strict weak ordering. All STL associative containers use this concept at some point, so all these specifications are meaningless with a partial ordering that is not a strict weak ordering.
Because a partial ordering (that is not a strict weak ordering) does not necessarily define any strict ordering, you cannot "sort elements" in the common sense according to partial ordering (all you can do is a "topological sort" which has weaker properties).
Given
a mathematical set S
a partial ordering < over S
a value x in S
you can define a partition of S (every element of S is either in L(x), I(x) or G(x)):
L(x) = { y in S | y<x }
I(x) = { y in S | not(y<x) and not(x<y) }
G(x) = { y in S | x<y }
L(x) : set of elements less than x
I(x) : set of elements incomparable with x
G(x) : set of elements greater than x
A sequence is sorted according to < iff for every x in the sequence, elements of L(x) appear first in the sequence, followed by elements of I(x), followed by elements of G(x).
A sequence is topologically sorted iff for every element y that appears after another element x in the sequence, y is not less than x. It is a weaker constraint than being sorted.
It is trivial to prove that every element of L(x) is less than any element of G(x). There is no general relation between elements of L(x) and elements of I(x), or between elements of I(x) and elements of G(x). However, if < is a strict weak ordering, than every element of L(x) is less than any element of I(x), and than any element of I(x) is less than any element of G(x).
If < is a strict weak ordering, and x<y then any element of L(x) U I(x) is less then any element I(y) U G(y): any element not greater than x is less than any element not less that y. This does not necessarily hold for a partial ordering.
Quoting the answer given here:
Because internally, these algorithms implement "is equal to" as !(a < b) && !(b < a).
If you used <= to implement the less-than operator, then the above
would return false when a == b. A broken equality check will screw up
nearly any algorithm.
Similarly, they implement "is not equal to" as (a < b) || (b < a)
, and once again, if you implemented the < operator using <=, then it
will return true when they are equal to each other, when in fact they
are not equal. So the equality check is broken in both directions.
The whole point of limiting the library to a less-than operator is
that all of the logical operators can be implemented in terms of it:
<(a, b): (a < b)
<=(a, b): !(b < a)
==(a, b): !(a < b) && !(b < a)
!=(a, b): (a < b) || (b < a)
>(a, b): (b < a)
>=(a, b): !(a < b)
This works as long as your provided operator meets the conditions of a
strict weak ordering. The standard <= and >= operators do not.
You cannot perform binary search with partial ordering. You cannot create a binary search tree with partial ordering. What functions/datatypes from algorithm need ordering and can work with partial ordering?
Related
Definition:
Let < be a binary relation where a < b means "a is less than b".
Let > be a binary relation where a > b means "a is greater than b".
So, we assume < and > have meanings we usually use in a daily life. Though, in some programming languages (e.g. C++), we can overload them to give them different definitions, hereafter we don't think about that.
Context:
As far I read mathematical definition of strict weak ordering (e.g. Wikipedia), I think both < and > satify it. However, all examples I saw in many websites refer only to <. There is even a website which says
what they roughly mean is that a Strict Weak Ordering has to behave the way that "less than" behaves: if a is less than b then b is not less than a, if a is less than b and b is less than c then a is less than c, and so on.
Also, in N4140 (C++14 International Standard), strict weak ordering is defines as
(§25.4-4) If we define equiv(a, b) as !comp(a, b) && !comp(b, a), then the requirements are that comp and equiv both be transitive relations
where comp is defined as
(§25.4-2) Compare is a function object type (20.9). The return value of the function call operation applied to an object of type Compare, when contextually converted to bool (Clause 4), yields true if the first argument of the call is less than the second, and false otherwise. Compare comp is used throughout for algorithms
assuming an ordering relation.
Question:
Does ">" satisfy strict weak ordering? I expect so, but have no confidence.
Does greater operator “>” satisfy strict weak ordering?
The mathematical strict greater than relation is a strict weak ordering.
As for the operator in C++ langauge: For all integers types: Yes. In general: No, but in most cases yes. Same applies to strict less than operator.
As for the confusing quote, "is less than" in that context intends to convey that means that the the end result of the sort operation is a non-decreasing sequence i.e. objects are "less" or equal to objects after them. If std::greater is used as comparison object, then greater values are "lesser" in order.
This may be confusing, but is not intended to exclude strict greater than operator.
what is the case where > doesn't satisfy strict weak ordering?
Some examples:
Overloaded operators that don't satisfy the properties.
> operator on pointers that do not point to the same array has unspecified result.
> does not satisfy irreflexivity requirement for floating point types in IEEE-754 representation unless NaNs are excluded from the domain.
Even if the standard refers to "less than" for arbitrary Compare functions, that only implies "less than" in the context of the ordering.
If I define an ordering by comparison function [](int a, int b) { return a > b; }, then an element is "less than" another in this ordering if its integer value is greater. That's because the ordering I've created is an ordering of the integers in reverse order. You shouldn't read < as "less than" in orderings. You should read it as "comes before".
Whenever x < y is a strict weak ordering then x > y is also a strict weak ordering, just with the reverse order.
Why does STL work with a comparison function that is strict weak ordering? Why can't it be partial ordering?
A partial order would not be sufficient to implement some algorithms, such as a sorting algorithm. Since a partially ordered set does not necessarily define a relationship between all elements of the set, how would you sort a list of two items that do not have an order relationship within the partial order?
Simply, a strict weak ordering is defined as an ordering that defines a (computable) equivalence relation. The equivalence classes are ordered by the strict weak ordering: a strict weak ordering is a strict ordering on equivalence classes.
A partial ordering (that is not a strict weak ordering) does not define an equivalence relation, so any specification using the concept of "equivalent elements" is meaningless with a partial ordering that is not a strict weak ordering. All STL associative containers use this concept at some point, so all these specifications are meaningless with a partial ordering that is not a strict weak ordering.
Because a partial ordering (that is not a strict weak ordering) does not necessarily define any strict ordering, you cannot "sort elements" in the common sense according to partial ordering (all you can do is a "topological sort" which has weaker properties).
Given
a mathematical set S
a partial ordering < over S
a value x in S
you can define a partition of S (every element of S is either in L(x), I(x) or G(x)):
L(x) = { y in S | y<x }
I(x) = { y in S | not(y<x) and not(x<y) }
G(x) = { y in S | x<y }
L(x) : set of elements less than x
I(x) : set of elements incomparable with x
G(x) : set of elements greater than x
A sequence is sorted according to < iff for every x in the sequence, elements of L(x) appear first in the sequence, followed by elements of I(x), followed by elements of G(x).
A sequence is topologically sorted iff for every element y that appears after another element x in the sequence, y is not less than x. It is a weaker constraint than being sorted.
It is trivial to prove that every element of L(x) is less than any element of G(x). There is no general relation between elements of L(x) and elements of I(x), or between elements of I(x) and elements of G(x). However, if < is a strict weak ordering, than every element of L(x) is less than any element of I(x), and than any element of I(x) is less than any element of G(x).
If < is a strict weak ordering, and x<y then any element of L(x) U I(x) is less then any element I(y) U G(y): any element not greater than x is less than any element not less that y. This does not necessarily hold for a partial ordering.
Quoting the answer given here:
Because internally, these algorithms implement "is equal to" as !(a < b) && !(b < a).
If you used <= to implement the less-than operator, then the above
would return false when a == b. A broken equality check will screw up
nearly any algorithm.
Similarly, they implement "is not equal to" as (a < b) || (b < a)
, and once again, if you implemented the < operator using <=, then it
will return true when they are equal to each other, when in fact they
are not equal. So the equality check is broken in both directions.
The whole point of limiting the library to a less-than operator is
that all of the logical operators can be implemented in terms of it:
<(a, b): (a < b)
<=(a, b): !(b < a)
==(a, b): !(a < b) && !(b < a)
!=(a, b): (a < b) || (b < a)
>(a, b): (b < a)
>=(a, b): !(a < b)
This works as long as your provided operator meets the conditions of a
strict weak ordering. The standard <= and >= operators do not.
You cannot perform binary search with partial ordering. You cannot create a binary search tree with partial ordering. What functions/datatypes from algorithm need ordering and can work with partial ordering?
I don't know much about the math behind strict weak ordering. But I've read something:
An operator that satisfies strict weak ordering can express all other
logical operators
<(a, b) : (a < b)
<=(a, b): !(b < a)
==(a, b): !(a < b) && !(b < a)
!=(a, b) : (a < b) || (b < a)
>(a, b) : (b < a)
>=(a, b): !(a < b)
So does it means that there's no need for "==" definition, since "<" satisfies strict weak ordering, which can express all logical operators? (This may obviously seem not correct because I've seen classes define both < and ==.)
An easy to understand explanation would be also much-appreciated :D. Not necessary to be too "mathematical" or "technically right".
Could someone give me 1 or 2 (if they are easy to find) examples that the standard library defines other operators based on <? A reference to source code would be good enough.
First thing's first. Defining operator < doesn't mean you get a definition of operator == for free from the compiler. It still needs to be defined explicitly.
The table above assumes something that is true for many types and ordering relations, but not for all. It assumes that equivalence between two elements implies equality. That need not hold.
We can certainly use < to check for equivalence between two elements. They are equivalent according to the strict week ordering (that's what !(a < b) && !(b < a) means). It doesn't necessarily mean those elements are equal.
A good example would be case insensitive comparison between strings of characters. In that case we will certainly have !("ab" < "AB") && !("AB" < "ab"), and the two string are equivalent, but they aren't equal in value.
Having said all that. If the order relation you defined implies all the other ones for your type, there are tricks to generate all the other operations from it. Just as the table demonstrates.
So does it means that there's no need for "==" definition?
You still need to define it if you want the clients of your class to use it. It can be implemented in terms of the < operator. It might be easier to implement == operator in terms of the < operator. However, it might be better to implement it cleanly by itself for performance reasons.
From the perspective of Algebraic Structure, there's a kind of binary relation called "Partially Ordered Set". A partially ordered set is defined as a binary relation that is reflexive, antisymmetric and transitive.
In C++, one could implement bool operator<(a, b) as a POS relationship operator. In this case, !(a < b) && !(b < a) can hardly be considered equivalent to equality. One possible implementation is testing if a node is a descendant of another node. This is a perfect example of a POS relationship.
I asked me how the cmp function in std::sort and std::is_sorted is defined.
here are two documentations for is_sorted_until how say it should be operator< :
en.cppreference.com
cplusplus.com
But i think there should be a problem with equal elements.
The list {1,1,1} should not be sorted because 1<1==false.
But there is an example which says:
...
int *sorted_end = std::is_sorted_until(nums, nums + N);
...
1 1 4 9 5 3 : 4 initial sorted elements
but that should return 1 if < is used like documented.
It would work with <=, but that is not the way it is documented.
I'm really confused.
The comparison is required to define a strict weak ordering. A strict weak ordering defines a set of equivalence classes from the incomparability relation, i.e., if x < y is false, and y < x is false too (i.e. x and y cannot be compared with <), x and y are considered equivalent. These equivalence classes have a total order, and that's the total order resulting from the sort functions.
In the example given, {1,1,1} has only a single equivalence class, the one composed of {1,1,1}.
is_sorted_until finds the first element x[i] for which x[i] < x[i-1] is true.
To be exact, it's neither < nor <=, it is defaulted to std::less. That one in turn calls < for most types, except where it is specialized. For example, < for pointers does not generally give a strict ordering, while std::less does.
It does indeed use operator< unless you provide a custom comparison. But the definition of "sorted" is not a[n] < a[n+1] (which we might call "strictly sorted"), but !(a[n+1] < a[n]); so equal elements are considered sorted. This is equivalent to using <=, but (in common with all other standard algorithms) doesn't require that operator to be defined.
In general, all ordered comparisons must define a "strict weak ordering". "Strict" means that the comparison must be false for equivalent objects; so < is valid, while <= is not.
If you look at the example implementation, < is used for checking if the next element is less than the previous one:
if (*next < *first)
return next;
If it is, then the order is broken, and the function returns. I. e. the logic is reversed - the algorithm does not terminate if the next element is equal to the previous.
C++ std::priority_queue just need a partial order. But if its implementation is a binary heap, how does it works?
For example: assume we have a partially ordered set ( {a, b, c, x}, {c < b, b < a, c < a} ), x has nothing to do with a, b, c. Then a max-heap is:
layer 1: x
layer 2: b x
layer 3: x x a c
After a pop operation, in a way commonly seen in text books, i.e. replace the root with c and decrease the size by 1. Then we need to heapify the tree below, at the root:
layer 1: c
layer 2: b x
layer 3: x x a
We will swap c and b as c < b, won't we? And what? We still don't have a valid heap since b < a. But b cannot "see" a.
The requirement for priority_queue is (§23.6.4 of the C++ Standard) that the comparator defines a strict, weak ordering. The latter is defined in §25.4/4 as follows:
The term strict refers to the requirement of an irreflexive relation (!comp(x, x) for all x), and the term weak to requirements that are not as strong as those for a total ordering, but stronger than those for a partial ordering. If we define equiv(a, b) as !comp(a, b) && !comp(b, a), then the requirements are that comp and equiv both be transitive relations:
— comp(a, b) && comp(b, c) implies comp(a, c)
— equiv(a, b) && equiv(b, c) implies equiv(a, c) [ Note: Under these conditions, it can be shown that
i) equiv is an equivalence relation
ii) comp induces a well-defined relation on the equivalence classes determined by equiv
iii) The induced relation is a strict total ordering. — end note ]
In other words, the comparator-defined relation does not have to be total, but it must be total with respect to the equivalence classes defined by a hypothetical relation equiv, which defines all elements as equal that are not less-than or greater-than each other.
To put it in even simpler terms, any elements not covered by the comparator relation will be treated as equal.