How can you use it?

May 19, 2023 · View on GitHub

This repository presents a quadratic strict weak ordering check. Strict weak ordering for a comparator compcomp and set SS means that it satisfies the conditions for all x,y,zSx, y, z \in S:

  1. Irreflexivity: comp(x,x)comp(x, x) must be false
  2. Antisymmetry: comp(x,y)comp(x, y) implies !comp(y,x)!comp(y, x)
  3. Transitivity: if comp(x,y)comp(x, y) and comp(y,z)comp(y, z) then comp(x,z)comp(x, z)
  4. Two objects x and y are equivalent if both comp(x,y)comp(x, y) and comp(y,x)comp(y, x) are false. Then transitivity of equivalence should be met.

Naive algorithm for steps 3 and 4 require O(S3)O(|S|^3) comparisons. However, there are faster algorithms to do that. In order to check if set SS satisfies this condition, we are going to do the following:

  1. Sort SS with some algorithm that correctly sorts it if SS satisfies strict weak ordering. And does not fail if it does not satisfy it. Bubble sort or heap sort should be good.

  2. Check that the range is sorted. If it's not, we know that SS does not satisfy strict weak ordering.

  3. If it's sorted, use the following algorithm:

  4. Find the minimal PP such that S[0]<S[P]S[0] < S[P]. If no such PP, set P to SIZE.

  5. For all A<B<PA < B < P check (!comp(S[A],S[B])(!comp(S[A], S[B]) and !comp(S[B],S[A]))!comp(S[B], S[A])), i.e. all elements before PP are equivalent.

  6. For all A<PA < P and BPB \geq P, check (comp(S[A],S[B])(comp(S[A], S[B]) and !comp(S[B],S[A]))!comp(S[B], S[A])), i.e. all elements separated by PP follows transitivity.

  7. Remove the first P elements from S. Go to step 2.

If any condition at step 2 and 3 is not met, return FALSE. It means strict weak ordering is not met.

The runtime of this algorithm is O(S2)O(|S|^2). Assume we eliminate x1,,xkx_1, \ldots, x_k elements on each iteration and thus we made

O(x_$1^{2}$ + x_1(|S| - x_1) + x_$2^{2}$ + x_2(|S| - x_1 - x_2) + \ldots) = O(x_1|S| + x_2|S| + \ldots) = O(|S| \times |S|)

comparisons.

How can you use it?

This functions can be used for testing the properties of the passed ranges. Suggested APIs:

  • bool strict_weak_ordering_check(first, last, settings, comp)

settings, comp arguments are not mandatory. We support C++11. settings support .prior_sort and some other implementation defined arguments. See test for examples.

$ clang++ test.cpp -g -std=c++11 -O3 -o test
$ clang++ -g fuzz.cpp -fsanitize=address,fuzzer -std=c++11 -O3 -o fuzz

In standard libraries this can be used to check if the range satisfies strict weak ordering for a sampled range of O(n)O(\sqrt{n}) elements without hurting too much performance.

Sortcheck repository found a lot of problems out there and this can contribute to find even more.

Proof

If strict weak ordering is met, the algorithm obviously returns TRUE. Assume it returns TRUE and strict weak ordering is not met.

Claim 1. If comp(S[i],S[j])comp(S[i], S[j]), then i<ji < j

Assume iji \geq j and comp(S[i],S[j])comp(S[i], S[j]). Then there is a moment when PP at step 1 exceeds element jj for the first time. Then it holds

$0 < j - \mathrm{removed index} = J < P$

Then element with index iremovedindex=I=Bi - \mathrm{removed index} = I = B and A=JA = J is checked at steps 2 or 3 and the opposite condition holds.

Claim 2. comp(S[i],S[i])comp(S[i], S[i]) is false

If it's true, then i<ii < i.

Claim 3. comp(S[i],S[j])comp(S[i], S[j]) and comp(S[j],S[k])comp(S[j], S[k]) implies comp(S[i],S[k])comp(S[i], S[k])

Assume the opposite, i.e. comp(S[i],S[j])comp(S[i], S[j]) and comp(S[j],S[k])comp(S[j], S[k]) and !comp(S[i],S[k])!comp(S[i], S[k]). According to claim 1, we have i<j<ki < j < k.

Find PP which exceeds element ii for the first time. Then at that step we have

  • comp(S[iremovedindex],S[jremovedindex])comp(S[i - \mathrm{removed index}], S[j - \mathrm{removed index}])
  • If PP exceeds element jj, then step 2 should return false
  • comp(S[jremovedindex],S[kremovedindex])comp(S[j - \mathrm{removed index}], S[k - \mathrm{removed index}])
  • If PP exceeds element kk, then step 2 should return false
  • It means P<jremovedindex<kremovedindexP < j - \mathrm{removed index} < k - \mathrm{removed index}. Then for A=iremovedindexA = i - \mathrm{removed index}, B=kremovedindexB = k - \mathrm{removed index} holds the desired condition.

Claim 4. Assymetry comp(S[i],S[j])comp(S[i], S[j]) implies !comp(S[j],S[i])!comp(S[j], S[i])

Obviously holds from claim 1: i<j<ii < j < i otherwise.

Claim 5. Transitivity of equivalence also holds

It means that !comp(S[I],S[J])!comp(S[I], S[J]), and !comp(S[J],S[I])!comp(S[J], S[I]), and !comp(S[J],S[K])!comp(S[J], S[K]), and !comp(S[K],S[J])!comp(S[K], S[J]), and ( comp(S[I],S[K])comp(S[I], S[K]) or comp(S[K],S[I])comp(S[K], S[I]) ).

Without loss of generality assume comp(S[I],S[K])comp(S[I], S[K]) (also means I<KI < K according to claim 1). If IJI \leq J, then

  • Find first PP which exceeds element at position II
  • If P>KP > K, then !comp(S[I],S[K])!comp(S[I], S[K]) according to step 2.
  • If PKP \leq K, then
    • If P>JP > J, then comp(S[J],S[K])comp(S[J], S[K]) according to step 3.
    • If PJP \leq J, then comp(S[I],S[J])comp(S[I], S[J]) according to step 3.

If J<IJ < I, then

  • Find first PP which exceeds element at position JJ
  • If K<PK < P, then I<K<PI < K < P and !comp(S[I],S[K])!comp(S[I], S[K])
  • If KPK \geq P, then comp(S[J],S[K])comp(S[J], S[K])