kblib 0.2.3
General utilities library for modern C++
sort.cpp
Go to the documentation of this file.
1#include "catch.hpp"
2
3#include "kblib/sort.h"
4
5#include "kblib/stats.h"
6#include "kblib/stringops.h"
7#include <iomanip>
8#include <iostream>
9#include <map>
10
11template <typename T, std::size_t N>
12constexpr auto sort_test(kblib::trivial_array<T, N> val) noexcept -> bool {
14 kblib::insertion_sort_copy(val.begin(), val.end(), out.begin(), out.end());
15 kblib::insertion_sort(val.begin(), val.end());
16 return true;
17}
18
19TEST_CASE("sort") {
20 constexpr kblib::trivial_array<int, 7> input{{3, 7, 4, 3, 1, 9, 5}};
21 const auto goal = [&] {
22 auto copy = input;
23 std::sort(copy.begin(), copy.end());
24 return copy;
25 }();
26
27 KBLIB_UNUSED auto print_arr = [&](auto c) {
28 for (const auto& v : c) {
29 std::cout << v << ", ";
30 }
31 std::cout << '\n';
32 };
33
34#if KBLIB_USE_CXX17
35 static_assert(sort_test(input), "insertion_sort should be constexpr");
36#endif
37
38 SECTION("insertion_sort") {
39 auto input_copy = input;
40 kblib::insertion_sort(input_copy.begin(), input_copy.end());
41 CHECK(input_copy == goal);
42 static_assert(
43 noexcept(kblib::insertion_sort(input_copy.begin(), input_copy.end())),
44 "insertion_sort for array<int> should be noexcept");
45 }
46 SECTION("insertion_sort_copy") {
47 std::remove_const<decltype(input)>::type output;
48 kblib::insertion_sort_copy(input.begin(), input.end(), output.begin(),
49 output.end());
50 CHECK(output == goal);
51 static_assert(
52 noexcept(kblib::insertion_sort_copy(input.begin(), input.end(),
53 output.begin(), output.end())),
54 "insertion_sort_copy for array<int> should be noexcept");
55 }
56 SECTION("adaptive_insertion_sort_copy") {
57 std::remove_const<decltype(input)>::type output;
58 kblib::adaptive_insertion_sort_copy(input.begin(), input.end(),
59 output.begin(), output.end());
60 CHECK(output == goal);
61 }
62 SECTION("insertion_sort is stable") {
63 std::minstd_rand rng{std::random_device{}()};
64 std::uniform_int_distribution<int> dist(0, 8);
65 for ([[gnu::unused]] auto _i : kblib::range(100)) {
66 // sort based on first key, second is used to distinguish between equal
67 // elements
68 std::vector<std::pair<int, int>> inputs;
69 auto pcomp = [](auto a, auto b) { return a.first < b.first; };
70
71 {
72 std::map<int, int> counts;
73 for ([[gnu::unused]] auto _j : kblib::range(100)) {
74 auto r = dist(rng);
75 inputs.emplace_back(r, counts[r]++);
76 }
77 }
78
79 kblib::insertion_sort(inputs.begin(), inputs.end(), pcomp);
80
81 {
82 std::map<int, int> counts;
83 for (auto p : inputs) {
84 CHECK(p.second == counts[p.first]++);
85 }
86 }
87 }
88 }
89 SECTION("insertion_sort on random data") {
90 std::minstd_rand rng{std::random_device{}()};
91 std::uniform_int_distribution<int> dist(0, 65535);
92 for ([[gnu::unused]] auto _i : kblib::range(100)) {
93 std::vector<int> input2(100);
94 std::generate(begin(input2), end(input2), [&] { return dist(rng); });
95
96 auto output_std = input2;
97 std::sort(output_std.begin(), output_std.end());
98 decltype(input2) output_kblib(input2.size());
99 kblib::insertion_sort_copy(cbegin(input2), cend(input2),
100 output_kblib.begin(), output_kblib.end());
101 CHECK(output_std == output_kblib);
102 }
103 }
104}
105
106static std::ostream& log_location = std::cout;
107
108auto linear(std::size_t i) { return static_cast<double>(i); }
109
110TEST_CASE("insertion sort performance") {
111 auto time_and_log
112 = [&](auto line, auto&& f, std::size_t quick = 30,
113 std::size_t slow = 10000, double (*O)(std::size_t) = linear) {
114 auto time_fast = f(quick) / O(quick);
115 auto time_slow = f(slow) / O(slow);
116 auto error = time_slow / time_fast;
117 log_location << __FILE__ ":" << std::left << line << ": \t"
118 << time_fast << "\t " << std::setw(12) << time_slow
119 << '\t' << std::setw(14) << error << '\t'
120 << time_slow * 10000 << '\t' << time_fast * 30 << "\n";
121
122 // Can't overshoot the bound by more than 5%:
123 CHECK(error < 1.05);
124 };
125#define TIME(...) time_and_log(__LINE__, __VA_ARGS__)
126
127 SECTION("labels") {
128 using namespace std::literals;
129 log_location << __FILE__ ":" << std::left << __LINE__
130 << ": \t/el (30)\t /el (10000)"
131 "\tsuperlinearity\ttotal (10000)\t"
132 "total (30)\n"
133 << kblib::repeat(" -"s, 60) << '\n';
134 }
135
136 SECTION("insertion_sort_copy on sorted data is fast") {
137 auto time_per = [](std::size_t size) {
138 std::vector<int> input(size);
139 decltype(input) output(size);
140 std::iota(input.begin(), input.end(), 0);
141
142 auto start = std::chrono::high_resolution_clock::now();
143 kblib::insertion_sort_copy(input.cbegin(), input.cend(),
144 output.begin(), output.end());
145 auto end = std::chrono::high_resolution_clock::now();
146 auto duration = end - start;
147 return static_cast<double>(duration.count());
148 };
149
150 TIME(time_per);
151 }
152 SECTION("insertion_sort_copy on reverse sorted data is slow") {
153 auto time_per = [](std::size_t size) {
154 std::vector<int> input(size);
155 decltype(input) output(size);
156 std::iota(input.rbegin(), input.rend(), 0);
157
158 auto start = std::chrono::high_resolution_clock::now();
159 kblib::insertion_sort_copy(input.cbegin(), input.cend(),
160 output.begin(), output.end());
161 auto end = std::chrono::high_resolution_clock::now();
162 auto duration = end - start;
163 return static_cast<double>(duration.count());
164 };
165
166 TIME(
167 time_per, 30, 1000, +[](std::size_t i) {
168 return static_cast<double>(i) * static_cast<double>(i);
169 });
170 }
171 SECTION("adaptive_insertion_sort_copy on sorted data is fast") {
172 auto time_per = [](std::size_t size) {
173 std::vector<int> input(size);
174 decltype(input) output(size);
175 std::iota(input.begin(), input.end(), 0);
176
177 auto start = std::chrono::high_resolution_clock::now();
178 kblib::adaptive_insertion_sort_copy(input.cbegin(), input.cend(),
179 output.begin(), output.end());
180 auto end = std::chrono::high_resolution_clock::now();
181 auto duration = end - start;
182 return static_cast<double>(duration.count());
183 };
184
185 TIME(time_per);
186 }
187 SECTION("adaptive_insertion_sort_copy on reverse sorted data is fast") {
188 auto time_per = [](std::size_t size) {
189 std::vector<int> input(size);
190 decltype(input) output(size);
191 std::iota(input.rbegin(), input.rend(), 0);
192
193 auto start = std::chrono::high_resolution_clock::now();
194 kblib::adaptive_insertion_sort_copy(input.cbegin(), input.cend(),
195 output.begin(), output.end());
196 auto end = std::chrono::high_resolution_clock::now();
197 auto duration = end - start;
198 return static_cast<double>(duration.count());
199 };
200
201 TIME(time_per);
202 }
203 SECTION("insertion_sort_copy on mostly sorted data is fast") {
204 std::minstd_rand rng{std::random_device{}()};
205 auto time_per = [&](int ssize, int noise) {
206 auto size = static_cast<std::size_t>(ssize);
207 std::uniform_int_distribution<int> dist(-noise, noise);
208 std::vector<int> input(size);
209 decltype(input) output(size);
210 for (auto i : kblib::range(ssize)) {
211 input[kblib::to_unsigned(i)] = i + dist(rng);
212 }
213
214 auto start = std::chrono::high_resolution_clock::now();
215 kblib::insertion_sort_copy(input.cbegin(), input.cend(),
216 output.begin(), output.end());
217 auto end = std::chrono::high_resolution_clock::now();
218 auto duration = end - start;
219 return static_cast<double>(duration.count());
220 };
221
222 auto n = 10000;
223 // deviation by +/- sqrt(n)
224 auto v = 100;
225
226 using namespace std::literals;
227 log_location << kblib::repeat(" -"s, 60) << '\n'
228 << __FILE__ ":" << std::left << __LINE__
229 << ": \t/el (s) \t /el (v) "
230 "\tsuperlinearity\ttotal (v) \t"
231 "total (s)\n"
232 << kblib::repeat(" -"s, 60) << '\n';
233
234 auto time_fast = time_per(n, 0) / n;
235 auto time_slow = time_per(n, v) / n;
236 auto ratio = time_slow / time_fast;
237 auto error = ratio / (n / v);
238
239 log_location << __FILE__ ":" << std::left << __LINE__ << ": \t"
240 << time_fast << "\t " << std::setw(12) << time_slow << '\t'
241 << std::setw(14) << error << '\t' << time_slow * 10000
242 << '\t' << time_fast * 30 << "\n";
243
244 // Can't overshoot the bound by more than 5%:
245 CHECK(error < 1.05);
246 }
247 log_location << std::flush;
248#undef TIME
249}
250
251TEST_CASE("byte extraction") {
252 constexpr std::uint32_t x{0xAB23CD67};
253
254 static_assert(std::numeric_limits<std::uint32_t>::digits
255 == sizeof(uint32_t) * CHAR_BIT,
256 "these checks assume uint32_t does not have padding.");
257 static_assert(CHAR_BIT == 8, "these checks assume 8-bit bytes");
258
259 static_assert(kblib::byte_count(x) == sizeof(x), "");
260 static_assert(+kblib::get_byte_index(x, 0) == 0x67, "");
261 static_assert(+kblib::get_byte_index(x, 1) == 0xCD, "");
262 static_assert(+kblib::get_byte_index(x, 2) == 0x23, "");
263 static_assert(+kblib::get_byte_index(x, 3) == 0xAB, "");
264
265 std::string str{"0123456789"};
266 CHECK(kblib::byte_count(str) == str.length());
267 for (auto i : kblib::range(str.length())) {
268 CHECK((kblib::get_byte_index(str, i)) == str[i]);
269 }
270
271 constexpr std::array<std::int32_t, 2> arr{0x10325476,
272 std::int32_t(0x98BADCFE)};
273 static_assert(kblib::byte_count(arr) == 8, "");
274 static_assert(+kblib::get_byte_index(arr, 0) == 0x76, "");
275 static_assert(+kblib::get_byte_index(arr, 1) == 0x54, "");
276 static_assert(+kblib::get_byte_index(arr, 2) == 0x32, "");
277 static_assert(+kblib::get_byte_index(arr, 3) == 0x10, "");
278 static_assert(+kblib::get_byte_index(arr, 4) == 0xFE, "");
279 static_assert(+kblib::get_byte_index(arr, 5) == 0xDC, "");
280 static_assert(+kblib::get_byte_index(arr, 6) == 0xBA, "");
281 static_assert(+kblib::get_byte_index(arr, 7) == 0x98, "");
282}
constexpr auto sort(RandomAccessIt, const RandomAccessIt, Compare) -> void
Definition: sort.h:364
constexpr auto size(const C &c) -> decltype(c.size())
Definition: fakestd.h:373
constexpr auto a(const std::initializer_list< T > &a) -> auto
Index an array literal without naming its type.
Definition: simple.h:255
constexpr auto generate(OutputIt first, EndIt last, Generator g) noexcept(noexcept(*++first=g())) -> OutputIt
Like std::generate except that it returns the output iterator at the end. It also allows for a sentin...
Definition: algorithm.h:1545
constexpr auto byte_count(T) noexcept -> enable_if_t< std::is_integral< T >::value, std::size_t >
Definition: sort.h:252
constexpr auto iota(ForwardIt first, ForwardIt last, T value) noexcept(noexcept(*first++=value) and noexcept(++value)) -> void
Definition: algorithm.h:1575
constexpr auto insertion_sort(const RandomAccessIt begin, const RandomAccessIt end, Compare &&compare={}) noexcept(noexcept(swap(*begin, *begin)) and noexcept(compare(*begin, *begin))) -> void
In-place insertion sort. As is usual, it is stable. Provides as a guarantee that it will perform no m...
Definition: sort.h:57
constexpr auto insertion_sort_copy(const RandomAccessIt begin, const RandomAccessIt end, const RandomAccessIt2 d_begin, const RandomAccessIt2 d_end, Compare &&compare={}) noexcept(noexcept(detail_algorithm::shift_backward(d_begin, d_begin, d_end)) and noexcept(*d_begin= *begin)) -> void
Out-of-place insertion sort, which does not modify the input. Provides as a guarantee that it will pe...
Definition: sort.h:93
constexpr auto adaptive_insertion_sort_copy(const RandomAccessIt begin, const RandomAccessIt end, const RandomAccessIt2 d_begin, const RandomAccessIt2 d_end, Compare &&compare={}) noexcept(noexcept(detail_algorithm::shift_backward(d_begin, d_begin, d_end)) and noexcept(*d_begin= *begin)) -> void
An adaptive sort which, at the cost of some additional work (time complexity Θ(sqrt(n))),...
Definition: sort.h:178
constexpr auto range(Value min, Value max, Delta step=0) -> range_t< Value, Delta >
Constructs a range from beginning, end, and step amount. The range is half-open, that is min is in th...
Definition: iterators.h:621
constexpr auto copy(InputIt first, EndIt last, OutputIt out) -> OutputIt
Copies all elements of [first, last) to out. It also allows for a sentinel end iterator.
Definition: algorithm.h:1322
constexpr auto get_byte_index(T x, std::size_t idx) noexcept -> enable_if_t< std::is_integral< T >::value, unsigned char >
Definition: sort.h:299
constexpr auto to_unsigned(I x) -> std::make_unsigned_t< I >
Cast integral argument to corresponding unsigned type.
Definition: fakestd.h:592
constexpr auto repeat(std::size_t N, Callable func) noexcept(noexcept(func())) -> return_assert_t< is_invocable< Callable >::value, void >
Invoke a function N times.
Definition: algorithm.h:53
#define TIME(...)
constexpr auto sort_test(kblib::trivial_array< T, N > val) noexcept -> bool
Definition: sort.cpp:12
auto linear(std::size_t i)
Definition: sort.cpp:108
TEST_CASE("sort")
Definition: sort.cpp:19
{WIP} Provides a fast and generic sorting interface.
Provides numerical and mathematical utilities.
Provides utilities for performing common operations on strings.
std::array isn't constexpr enough in C++14, so a dedicated array class is needed for constexpr functi...
Definition: stats.h:84
#define KBLIB_UNUSED
This internal macro is used to provide a fallback for [[maybe_unused]] in C++14.
Definition: tdecl.h:130