Skip to content

Commit 87ca147

Browse files
committed
Add measure of presortedness: spear
1 parent 68427ff commit 87ca147

File tree

10 files changed

+257
-5
lines changed

10 files changed

+257
-5
lines changed

docs/Measures-of-presortedness.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,22 @@ Computes the number of non-decreasing runs in *X* minus one.
275275

276276
`max_for_size`: |*X*| - 1 when *X* is sorted in reverse order.
277277

278+
### *Spear*
279+
280+
```cpp
281+
#include <cpp-sort/probes/spear.h>
282+
```
283+
284+
Spearman's footrule distance: sum of distances between the position of individual elements in *X* and their position once *X* is sorted. Its use a a measure of presortedness was proposed by P. Diaconis and R. L. Graham in *Spearman's Footrule as a Measure of Disarray*.
285+
286+
| Complexity | Memory | Iterators |
287+
| ----------- | ----------- | ------------- |
288+
| n log n | n | Forward |
289+
290+
`max_for_size`: |*X*|²/2 when *X* is sorted in reverse order.
291+
292+
*New in version 1.17.0*
293+
278294
### *SUS*
279295

280296
```cpp
@@ -297,6 +313,14 @@ Computes the minimum number of non-decreasing subsequences (of possibly not adja
297313

298314
Some additional measures of presortedness how been described in the literature but do not appear in the partial ordering graph. This section describes some of them but is not an exhaustive list.
299315

316+
### *DS*
317+
318+
A measure called *DS* appears in *Computing and ranking measures of presortedness* by J. Chen, and in some literature about measures of presortedness online (including some earlier versions of this documentation). The measure corresponds to the one we call *Spear* in the library.
319+
320+
*Spear* is introduced under the name *D*, likely for (Spearman's Footrule) *Distance*, in *Spearman's Footrule as a Measure of Disarray* by P. Diaconis and R. L. Graham. Other sources give the name $D_S$, and similary give the name $D_H$ to *Ham*, for Hamming distance. I believe that the name *DS* comes from there.
321+
322+
In other domains, that value is called *F* (for *Footrule*). It is no more helpful a name than *D* or *DS*, so I decided to use *Spear* for this library's name (for *Spearman*) - following the same naming pattern that led to *Ham* -, despite there being no precedent in the literature.
323+
300324
### *Par*
301325

302326
*Par* was described by V. Estivill-Castro and D. Wood in *A New Measure of Presortedness* as follows:
10.2 KB
Loading

include/cpp-sort/probes.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2021 Morwenn
2+
* Copyright (c) 2016-2025 Morwenn
33
* SPDX-License-Identifier: MIT
44
*/
55
#ifndef CPPSORT_PROBES_H_
@@ -20,6 +20,7 @@
2020
#include <cpp-sort/probes/par.h>
2121
#include <cpp-sort/probes/rem.h>
2222
#include <cpp-sort/probes/runs.h>
23+
#include <cpp-sort/probes/spear.h>
2324
#include <cpp-sort/probes/sus.h>
2425

2526
#endif // CPPSORT_PROBES_H_

include/cpp-sort/probes/spear.h

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright (c) 2025 Morwenn
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
#ifndef CPPSORT_PROBES_SPEAR_H_
6+
#define CPPSORT_PROBES_SPEAR_H_
7+
8+
////////////////////////////////////////////////////////////
9+
// Headers
10+
////////////////////////////////////////////////////////////
11+
#include <functional>
12+
#include <iterator>
13+
#include <utility>
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 <cpp-sort/utility/size.h>
19+
#include <cpp-sort/utility/static_const.h>
20+
#include "../detail/equal_range.h"
21+
#include "../detail/immovable_vector.h"
22+
#include "../detail/iterator_traits.h"
23+
#include "../detail/pdqsort.h"
24+
#include "../detail/type_traits.h"
25+
26+
namespace cppsort
27+
{
28+
namespace probe
29+
{
30+
namespace detail
31+
{
32+
template<typename ForwardIterator, typename Compare, typename Projection>
33+
auto spear_probe_algo(ForwardIterator first, ForwardIterator last,
34+
cppsort::detail::difference_type_t<ForwardIterator> size,
35+
Compare compare, Projection projection)
36+
-> ::cppsort::detail::difference_type_t<ForwardIterator>
37+
{
38+
using difference_type = ::cppsort::detail::difference_type_t<ForwardIterator>;
39+
auto&& proj = utility::as_function(projection);
40+
41+
if (size < 2) {
42+
return 0;
43+
}
44+
45+
////////////////////////////////////////////////////////////
46+
// Indirectly sort the iterators
47+
48+
// Copy the iterators in a vector
49+
cppsort::detail::immovable_vector<ForwardIterator> iterators(size);
50+
for (auto it = first; it != last; ++it) {
51+
iterators.emplace_back(it);
52+
}
53+
54+
// Sort the iterators on pointed values
55+
cppsort::detail::pdqsort(
56+
iterators.begin(), iterators.end(),
57+
compare, utility::indirect{} | projection
58+
);
59+
60+
////////////////////////////////////////////////////////////
61+
// Sum of distances between an elements and their sorted
62+
// positions
63+
64+
difference_type sum_dist = 0;
65+
difference_type it_pos = 0;
66+
for (auto it = first; it != last; ++it) {
67+
// Find the range where *it belongs once sorted
68+
auto rng = cppsort::detail::equal_range(
69+
iterators.begin(), iterators.end(), proj(*it),
70+
compare, utility::indirect{} | projection
71+
);
72+
auto pos_min = rng.first - iterators.begin();
73+
auto pos_max = rng.second - iterators.begin();
74+
75+
// If *it isn't into one of its sorted positions, computed the closest one
76+
if (it_pos < pos_min) {
77+
sum_dist += pos_min - it_pos;
78+
} else if (it_pos >= pos_max) {
79+
sum_dist += it_pos - pos_max + 1;
80+
}
81+
82+
++it_pos;
83+
}
84+
return sum_dist;
85+
}
86+
87+
struct spear_impl
88+
{
89+
template<
90+
typename ForwardIterable,
91+
typename Compare = std::less<>,
92+
typename Projection = utility::identity,
93+
typename = cppsort::detail::enable_if_t<
94+
is_projection_v<Projection, ForwardIterable, Compare>
95+
>
96+
>
97+
auto operator()(ForwardIterable&& iterable,
98+
Compare compare={}, Projection projection={}) const
99+
-> decltype(auto)
100+
{
101+
return spear_probe_algo(std::begin(iterable), std::end(iterable),
102+
utility::size(iterable),
103+
std::move(compare), std::move(projection));
104+
}
105+
106+
template<
107+
typename ForwardIterator,
108+
typename Compare = std::less<>,
109+
typename Projection = utility::identity,
110+
typename = cppsort::detail::enable_if_t<
111+
is_projection_iterator_v<Projection, ForwardIterator, Compare>
112+
>
113+
>
114+
auto operator()(ForwardIterator first, ForwardIterator last,
115+
Compare compare={}, Projection projection={}) const
116+
-> decltype(auto)
117+
{
118+
auto dist = std::distance(first, last);
119+
return spear_probe_algo(std::move(first), std::move(last), dist,
120+
std::move(compare), std::move(projection));
121+
}
122+
123+
template<typename Integer>
124+
static constexpr auto max_for_size(Integer n)
125+
-> Integer
126+
{
127+
return n * n / 2;
128+
}
129+
};
130+
}
131+
132+
namespace
133+
{
134+
constexpr auto&& spear = utility::static_const<
135+
sorter_facade<detail::spear_impl>
136+
>::value;
137+
}
138+
}}
139+
140+
#endif // CPPSORT_PROBES_SPEAR_H_

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ add_executable(main-tests
198198
probes/osc.cpp
199199
probes/rem.cpp
200200
probes/runs.cpp
201+
probes/spear.cpp
201202
probes/sus.cpp
202203
probes/relations.cpp
203204
probes/every_probe_common.cpp

tests/probes/every_probe_common.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2022 Morwenn
2+
* Copyright (c) 2021-2025 Morwenn
33
* SPDX-License-Identifier: MIT
44
*/
55
#include <numeric>
@@ -24,6 +24,7 @@ TEMPLATE_TEST_CASE( "test every probe with all_equal distribution", "[probe]",
2424
decltype(cppsort::probe::osc),
2525
decltype(cppsort::probe::rem),
2626
decltype(cppsort::probe::runs),
27+
decltype(cppsort::probe::spear),
2728
decltype(cppsort::probe::sus) )
2829
{
2930
// Ensure that all measures of presortedness return 0 when
@@ -47,6 +48,7 @@ TEMPLATE_TEST_CASE( "test every probe with a sorted collection", "[probe]",
4748
decltype(cppsort::probe::osc),
4849
decltype(cppsort::probe::rem),
4950
decltype(cppsort::probe::runs),
51+
decltype(cppsort::probe::spear),
5052
decltype(cppsort::probe::sus) )
5153
{
5254
// Ensure that all measures of presortedness return 0 when
@@ -71,6 +73,7 @@ TEMPLATE_TEST_CASE( "test every probe with a 0 or 1 element", "[probe]",
7173
decltype(cppsort::probe::osc),
7274
decltype(cppsort::probe::rem),
7375
decltype(cppsort::probe::runs),
76+
decltype(cppsort::probe::spear),
7477
decltype(cppsort::probe::sus) )
7578
{
7679
// Ensure that all measures of presortedness return 0 when

tests/probes/every_probe_move_compare_projection.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2022 Morwenn
2+
* Copyright (c) 2020-2025 Morwenn
33
* SPDX-License-Identifier: MIT
44
*/
55
#include <iterator>
@@ -21,6 +21,7 @@ TEMPLATE_TEST_CASE( "every probe with comparison function altered by move", "[pr
2121
decltype(cppsort::probe::osc),
2222
decltype(cppsort::probe::rem),
2323
decltype(cppsort::probe::runs),
24+
decltype(cppsort::probe::spear),
2425
decltype(cppsort::probe::sus) )
2526
{
2627
std::vector<int> collection; collection.reserve(491);
@@ -44,6 +45,7 @@ TEMPLATE_TEST_CASE( "every probe with projection function altered by move", "[pr
4445
decltype(cppsort::probe::osc),
4546
decltype(cppsort::probe::rem),
4647
decltype(cppsort::probe::runs),
48+
decltype(cppsort::probe::spear),
4749
decltype(cppsort::probe::sus) )
4850
{
4951
std::vector<int> collection; collection.reserve(491);

tests/probes/relations.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2022 Morwenn
2+
* Copyright (c) 2016-2025 Morwenn
33
* SPDX-License-Identifier: MIT
44
*/
55
#include <iterator>
@@ -34,6 +34,7 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" )
3434
auto osc = cppsort::probe::osc(sequence);
3535
auto rem = cppsort::probe::rem(sequence);
3636
auto runs = cppsort::probe::runs(sequence);
37+
auto spear = cppsort::probe::spear(sequence);
3738
auto sus = cppsort::probe::sus(sequence);
3839

3940
// Measures of Presortedness and Optimal Sorting Algorithms
@@ -91,6 +92,11 @@ TEST_CASE( "relations between measures of presortedness", "[probe]" )
9192
// by Jingsen Chen
9293
CHECK( enc <= dis + 1 );
9394

95+
// Spearman's Footrule as a Measure of Disarray
96+
// by Persi Diaconis and Ronald Lewis Graham
97+
CHECK( inv + exc <= spear );
98+
CHECK( spear <= 2 * inv );
99+
94100
// Intuitive result: a descending run can be seen as several
95101
// ascending runs
96102
CHECK( mono <= runs );

tests/probes/spear.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (c) 2025 Morwenn
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
#include <forward_list>
6+
#include <initializer_list>
7+
#include <iterator>
8+
#include <vector>
9+
#include <catch2/catch_test_macros.hpp>
10+
#include <cpp-sort/probes/spear.h>
11+
#include <cpp-sort/utility/size.h>
12+
#include <testing-tools/distributions.h>
13+
#include <testing-tools/internal_compare.h>
14+
15+
TEST_CASE( "presortedness measure: spear", "[probe][max]" )
16+
{
17+
using cppsort::probe::spear;
18+
19+
SECTION( "simple test" )
20+
{
21+
std::forward_list<int> li = { 12, 28, 17, 59, 13, 10, 39, 21, 31, 30 };
22+
CHECK( spear(li) == 28 );
23+
CHECK( spear(li.begin(), li.end()) == 28 );
24+
25+
std::vector<internal_compare<int>> tricky(li.begin(), li.end());
26+
CHECK( spear(tricky, &internal_compare<int>::compare_to) == 28 );
27+
}
28+
29+
SECTION( "upper bound" )
30+
{
31+
// The upper bound should be n²/2 when the collection
32+
// is sorted in reverse order
33+
34+
std::forward_list<int> li = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
35+
auto max_n = spear.max_for_size(cppsort::utility::size(li));
36+
CHECK( max_n == 60 );
37+
CHECK( spear(li) == max_n );
38+
CHECK( spear(li.begin(), li.end()) == max_n );
39+
}
40+
41+
SECTION( "exhaustive check for 4 values" )
42+
{
43+
// Results from Spearman's Footrule as a Measure of Disarray
44+
// by Diaconis and Graham
45+
46+
auto spear = [](std::vector<int> vec) {
47+
return cppsort::probe::spear(vec);
48+
};
49+
50+
CHECK( spear({1, 2, 3, 4}) == 0 );
51+
CHECK( spear({1, 2, 4, 3}) == 2 );
52+
CHECK( spear({1, 3, 2, 4}) == 2 );
53+
CHECK( spear({1, 3, 4, 2}) == 4 );
54+
CHECK( spear({1, 4, 2, 3}) == 4 );
55+
CHECK( spear({1, 4, 3, 2}) == 4 );
56+
CHECK( spear({2, 1, 3, 4}) == 2 );
57+
CHECK( spear({2, 1, 4, 3}) == 4 );
58+
CHECK( spear({2, 3, 1, 4}) == 4 );
59+
CHECK( spear({2, 3, 4, 1}) == 6 );
60+
CHECK( spear({2, 4, 1, 3}) == 6 );
61+
CHECK( spear({2, 4, 3, 1}) == 6 );
62+
CHECK( spear({3, 1, 2, 4}) == 4 );
63+
CHECK( spear({3, 1, 4, 2}) == 6 );
64+
CHECK( spear({3, 2, 1, 4}) == 4 );
65+
CHECK( spear({3, 2, 4, 1}) == 6 );
66+
CHECK( spear({3, 4, 1, 2}) == 8 );
67+
CHECK( spear({3, 4, 2, 1}) == 8 );
68+
CHECK( spear({4, 1, 2, 3}) == 6 );
69+
CHECK( spear({4, 1, 3, 2}) == 6 );
70+
CHECK( spear({4, 2, 1, 3}) == 6 );
71+
CHECK( spear({4, 2, 3, 1}) == 6 );
72+
CHECK( spear({4, 3, 1, 2}) == 8 );
73+
CHECK( spear({4, 3, 2, 1}) == 8 );
74+
}
75+
}

tools/mops-partial-ordering.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
\node[state] (osc) [below of=loc] {$\bm{Osc}$};
3535
\node[state] (enc) [below of=sms] {$\bm{Enc}$};
3636
\node[state] (rem) [below of=block] {$\bm{Rem}$};
37-
\node[state] (inv) [below of=osc] {$\bm{Inv}~$$\equiv$$~DS$};
37+
\node[state] (inv) [below of=osc] {$\bm{Inv}~$$\equiv$$~\bm{Spear}$};
3838
\node[state] (sus) [below of=enc] {$\bm{SUS}$};
3939
\node[state] (exc) [below of=rem] {$\bm{Exc}~$$\equiv$$~\bm{Ham}$};
4040
\node[state] (max) [below of=inv] {$\bm{Max}~$$\equiv$$~\bm{Dis}~$};

0 commit comments

Comments
 (0)