Skip to content

Commit 7ed94c6

Browse files
committed
New measure of disorder: Amp
1 parent 2e8c3ad commit 7ed94c6

12 files changed

+221
-24
lines changed

docs/Measures-of-disorder.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ The graph below shows the partial ordering of several measures of disorder:
8383
- *m₀* is a measure of presortedness that always returns 0.
8484
- *m₀₁* is a measure of presortedness that returns 0 when $X$ is sorted and 1 otherwise.
8585

86-
![Partial ordering of measures of disorder](images/mops-partial-ordering.png)
86+
![Partial ordering of measures of disorder](images/partial-ordering-measures-of-disorder.png)
8787

88-
This graph is a modified version of the one in *A framework for adaptive sorting*. The relations of *Mono* are empirically derived [original research][original-research] and incomplete (unknown relations with *Osc* and *Loc*).
88+
This graph is a modified version of the one found in *A framework for adaptive sorting*. The relations of *Mono* and *Amp* with other measures of disorder are empirically derived [original research][original-research] and known to be incomplete (unknown relations with *Osc* and *Loc*).
8989

9090
The measures of disorder in bold in the graph are available in **cpp-sort**, the others are not.
9191

@@ -133,7 +133,40 @@ It takes an integer `n` and returns the maximum value that the measure of disord
133133
134134
## Available measures of disorder
135135
136-
Measures of disorder are pretty formalized, so the names of the functions in the library are short and generally correspond to the ones used in the literature.
136+
Measures of disorder are pretty formalized, so the names of the functions in the library are short and generally correspond to the ones used in the literature, with a few exceptions. A justification is given whenever a name does not exactly match the ones from the literature, or when the definition differs.
137+
138+
### *Amp*
139+
140+
```cpp
141+
#include <cpp-sort/probes/amp.h>
142+
```
143+
144+
Let's consider the following functions to compare two elements elements of a sequence:
145+
146+
$$
147+
comp(x, y)=
148+
\begin{cases}
149+
1 & \text{ if } x \lt y\\
150+
-1 & \text{ if } x \gt y\\
151+
0 & \text{otherwise}
152+
\end{cases}
153+
$$
154+
155+
We define $\mathit{Amp}(X)$ as follows:
156+
157+
$$\mathit{Amp}(X) = \lvert X \rvert - \mathit{PTP}(X) - N_{\mathit{eq}}(X) - 1$$
158+
159+
Where $N_{\mathit{eq}}(X)$ is the number of pairs of neighbors that compare equivalent in $X$, and $\mathit{PTP}(X)$ is the number of unique values in the prefix sum of the sequence obtained by applying $comp$ to every pair of adjacent elements in $X$.
160+
161+
![Illustration of how comp is applied to pairs of neighbors up to the prefix sum](images/pairwise-order-shadow.png)
162+
163+
| Complexity | Memory | Iterators | Monotonic |
164+
| ----------- | ----------- | ------------- | --------- |
165+
| n | 1 | Forward | No |
166+
167+
`max_for_size`: $\lvert X \rvert - 2$ when the sign of $comp$ changes for every pair of neighbors.
168+
169+
**Note:** *Amp* does not respect Mannila's criterion 4: $\mathit{Amp}(\langle 1, 2, 3 \rangle) = 0$ and $\mathit{Amp}(\langle 6, 5, 4 \rangle) = 0$, but $\mathit{Amp}(\langle 1, 2, 3, 6, 5, 4 \rangle) = 4$.
137170

138171
### *Block*
139172

-38.6 KB
Binary file not shown.
13.2 KB
Loading
41 KB
Loading

include/cpp-sort/probes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
////////////////////////////////////////////////////////////
99
// Headers
1010
////////////////////////////////////////////////////////////
11+
#include <cpp-sort/probes/amp.h>
1112
#include <cpp-sort/probes/block.h>
1213
#include <cpp-sort/probes/dis.h>
1314
#include <cpp-sort/probes/enc.h>

include/cpp-sort/probes/amp.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (c) 2025 Morwenn
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
#ifndef CPPSORT_PROBES_AMP_H_
6+
#define CPPSORT_PROBES_AMP_H_
7+
8+
////////////////////////////////////////////////////////////
9+
// Headers
10+
////////////////////////////////////////////////////////////
11+
#include <algorithm>
12+
#include <functional>
13+
#include <iterator>
14+
#include <cpp-sort/sorter_facade.h>
15+
#include <cpp-sort/sorter_traits.h>
16+
#include <cpp-sort/utility/as_function.h>
17+
#include <cpp-sort/utility/functional.h>
18+
#include "../detail/iterator_traits.h"
19+
#include "../detail/type_traits.h"
20+
21+
namespace cppsort
22+
{
23+
namespace probe
24+
{
25+
namespace detail
26+
{
27+
struct amp_impl
28+
{
29+
template<
30+
typename ForwardIterator,
31+
typename Compare = std::less<>,
32+
typename Projection = utility::identity,
33+
typename = cppsort::detail::enable_if_t<
34+
is_projection_iterator_v<Projection, ForwardIterator, Compare>
35+
>
36+
>
37+
auto operator()(ForwardIterator first, ForwardIterator last,
38+
Compare compare={}, Projection projection={}) const
39+
-> cppsort::detail::difference_type_t<ForwardIterator>
40+
{
41+
using difference_type = cppsort::detail::difference_type_t<ForwardIterator>;
42+
auto&& comp = utility::as_function(compare);
43+
auto&& proj = utility::as_function(projection);
44+
45+
if (first == last || std::next(first) == last) {
46+
return 0;
47+
}
48+
49+
difference_type size = 0,
50+
shadow = 0,
51+
min = 0,
52+
max = 0;
53+
54+
auto current = first;
55+
auto next = std::next(current);
56+
do {
57+
++size;
58+
59+
if (comp(proj(*current), proj(*next))) {
60+
max = (std::max)(max, ++shadow);
61+
} else if (comp(proj(*next), proj(*current))) {
62+
min = (std::min)(min, --shadow);
63+
} else {
64+
// Neighbours that compare equivalent don't contribute to the amplitude
65+
--size;
66+
}
67+
68+
++current;
69+
++next;
70+
} while (next != last);
71+
72+
return size - (max - min);
73+
}
74+
75+
template<typename Integer>
76+
static constexpr auto max_for_size(Integer n)
77+
-> Integer
78+
{
79+
return n <= 2 ? 0 : n - 2;
80+
}
81+
};
82+
}
83+
84+
inline constexpr sorter_facade<detail::amp_impl> amp{};
85+
}}
86+
87+
#endif // CPPSORT_PROBES_AMP_H_

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ add_executable(main-tests
209209
distributions/shuffled_16_values.cpp
210210

211211
# Probes tests
212+
probes/amp.cpp
212213
probes/block.cpp
213214
probes/dis.cpp
214215
probes/enc.cpp

tests/probes/amp.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2025 Morwenn
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
#include <forward_list>
6+
#include <vector>
7+
#include <catch2/catch_test_macros.hpp>
8+
#include <rapidcheck.h>
9+
#include <rapidcheck/catch.h>
10+
#include <cpp-sort/probes/amp.h>
11+
#include <cpp-sort/utility/size.h>
12+
#include <testing-tools/internal_compare.h>
13+
14+
TEST_CASE( "measure of disorder: amp", "[probe][amp]" )
15+
{
16+
using cppsort::probe::amp;
17+
18+
SECTION( "simple test" )
19+
{
20+
std::forward_list<int> li = { 4, 6, 5, 2, 9, 1, 3, 8, 0, 7 };
21+
CHECK( amp(li) == 7 );
22+
CHECK( amp(li.begin(), li.end()) == 7 );
23+
24+
std::vector<internal_compare<int>> tricky(li.begin(), li.end());
25+
CHECK( amp(tricky, &internal_compare<int>::compare_to) == 7 );
26+
}
27+
28+
SECTION( "upper bound" )
29+
{
30+
// The upper bound should correspond to a sequence that
31+
// oscillates at every step
32+
33+
std::forward_list<int> li = { 0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9 };
34+
auto max_n = amp.max_for_size(cppsort::utility::size(li));
35+
CHECK( max_n == 9 );
36+
CHECK( amp(li) == max_n );
37+
CHECK( amp(li.begin(), li.end()) == max_n );
38+
}
39+
40+
// https://morwenn.github.io/presortedness/2025/10/18/TSB005-symmetry-of-amp.html
41+
rc::prop("Amp(Reversed(X)) = Amp(X)", [](std::vector<int> sequence) {
42+
auto amp_x = amp(sequence);
43+
std::reverse(sequence.begin(), sequence.end());
44+
return amp(sequence) == amp_x;
45+
});
46+
}

tests/probes/every_probe_common.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//
1919

2020
TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]",
21+
decltype(cppsort::probe::amp),
2122
decltype(cppsort::probe::block),
2223
decltype(cppsort::probe::dis),
2324
decltype(cppsort::probe::enc),
@@ -43,6 +44,7 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]",
4344
}
4445

4546
TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]",
47+
decltype(cppsort::probe::amp),
4648
decltype(cppsort::probe::block),
4749
decltype(cppsort::probe::dis),
4850
decltype(cppsort::probe::enc),
@@ -69,6 +71,7 @@ TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]",
6971
}
7072

7173
TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]",
74+
decltype(cppsort::probe::amp),
7275
decltype(cppsort::probe::block),
7376
decltype(cppsort::probe::dis),
7477
decltype(cppsort::probe::enc),
@@ -107,6 +110,7 @@ TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]",
107110
}
108111

109112
TEMPLATE_TEST_CASE( "test order isomorphism for every probe", "[probe]",
113+
decltype(cppsort::probe::amp),
110114
decltype(cppsort::probe::block),
111115
decltype(cppsort::probe::dis),
112116
decltype(cppsort::probe::enc),
@@ -166,6 +170,7 @@ namespace
166170
}
167171

168172
TEMPLATE_TEST_CASE( "test M(subsequence(X)) <= M(X) for most probes M", "[probe]",
173+
decltype(cppsort::probe::amp),
169174
decltype(cppsort::probe::dis),
170175
decltype(cppsort::probe::enc),
171176
decltype(cppsort::probe::inv),
@@ -279,7 +284,7 @@ TEMPLATE_TEST_CASE( "test M(2, 1, 4, 3, 6, 5, ...) <= |X| * M(2, 1) / 2 for most
279284
{
280285
// From *Sorting and Measures of Disorder* by Estivill-Castro:
281286
// property derived from Mannila's criteria 2 & 4
282-
// The following probes don't satisfy it: Block, Mono, Osc
287+
// The following probes don't satisfy it: Amp, Block, Mono, Osc
283288

284289
int size = 1000;
285290
std::vector<int> sequence(size, 0);
@@ -295,6 +300,7 @@ TEMPLATE_TEST_CASE( "test M(2, 1, 4, 3, 6, 5, ...) <= |X| * M(2, 1) / 2 for most
295300
}
296301

297302
TEMPLATE_TEST_CASE( "test M(aX) <= |X| + M(X) for most probes M", "[probe]",
303+
decltype(cppsort::probe::amp),
298304
decltype(cppsort::probe::block),
299305
decltype(cppsort::probe::dis),
300306
decltype(cppsort::probe::enc),
@@ -335,7 +341,7 @@ TEMPLATE_TEST_CASE( "test prefix monotonicity", "[probe]",
335341
decltype(cppsort::probe::sus) )
336342
{
337343
// Property formalized by Estivill-Castro in *Sorting and Measures of Disorder*
338-
// The following probes don't satisfy it: Block, Mono, Osc
344+
// The following probes don't satisfy it: Amp, Block, Mono, Osc
339345

340346
// Note: the original paper claims that Osc also satisfies this property,
341347
// but it fails for X=⟨3, 0⟩ Y=⟨⟩ Z=⟨4, 2⟩
@@ -383,7 +389,7 @@ TEMPLATE_TEST_CASE( "test monotonicity", "[probe]",
383389
decltype(cppsort::probe::sus) )
384390
{
385391
// Property formalized by Estivill-Castro in *Sorting and Measures of Disorder*
386-
// The following probes don't satisfy it: Block, Enc, Mono, Osc
392+
// The following probes don't satisfy it: Amp, Block, Enc, Mono, Osc
387393

388394
// Note: the original paper claims that MEnc[k,A,D] also satisfies this property,
389395
// but at the time of writing this comment I ahev no idea what that means
@@ -424,6 +430,7 @@ TEMPLATE_TEST_CASE( "test monotonicity", "[probe]",
424430
}
425431

426432
TEMPLATE_TEST_CASE( "test that probes never produce more disorder than their theoretical maximum", "[probe]",
433+
decltype(cppsort::probe::amp),
427434
decltype(cppsort::probe::block),
428435
decltype(cppsort::probe::dis),
429436
decltype(cppsort::probe::enc),

tests/probes/every_probe_heap_memory_exhaustion.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
//
2121

2222
TEMPLATE_TEST_CASE( "heap exhaustion for random-access probes", "[probe][heap_exhaustion]",
23+
decltype(cppsort::probe::amp),
2324
decltype(cppsort::probe::dis),
2425
decltype(cppsort::probe::mono),
2526
decltype(cppsort::probe::runs) )
@@ -38,6 +39,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for random-access probes", "[probe][heap_ex
3839
}
3940

4041
TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_exhaustion]",
42+
decltype(cppsort::probe::amp),
4143
decltype(cppsort::probe::dis),
4244
decltype(cppsort::probe::mono),
4345
decltype(cppsort::probe::runs) )
@@ -56,6 +58,7 @@ TEMPLATE_TEST_CASE( "heap exhaustion for bidirectional probes", "[probe][heap_ex
5658
}
5759

5860
TEMPLATE_TEST_CASE( "heap exhaustion for forward probes", "[probe][heap_exhaustion]",
61+
decltype(cppsort::probe::amp),
5962
decltype(cppsort::probe::dis),
6063
decltype(cppsort::probe::mono),
6164
decltype(cppsort::probe::runs) )

0 commit comments

Comments
 (0)