Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
stable_sort.hpp
Go to the documentation of this file.
1#pragma once
2
3// Stable, in-place, allocation-free sort for audio-thread event lists.
4//
5// std::stable_sort may allocate a temporary buffer and std::sort reorders
6// equal keys, so neither suits a render callback that must keep a host's
7// order for coincident events. This sorts blocks of 20 by insertion and
8// merges them with the SymMerge algorithm (Kim and Kutzner): O(n log^2 n)
9// compares and moves, recursion depth O(log n), no scratch storage. Input
10// already in order is detected in one pass and left untouched. A caller that
11// can lend storage and sorts on a small integer key (a sample offset within
12// a block) uses stable_counting_sort instead: O(n + range).
13
14#include <algorithm>
15#include <cstddef>
16#include <iterator>
17#include <span>
18#include <utility>
19
20namespace pulp::state {
21
23
24template <typename It, typename Less>
25void insertion_sort(It base, std::size_t a, std::size_t b, Less& less) {
26 for (std::size_t i = a + 1; i < b; ++i)
27 for (std::size_t j = i; j > a && less(base[j], base[j - 1]); --j)
28 std::iter_swap(base + j, base + (j - 1));
29}
30
31// Merges the sorted ranges [a, m) and [m, b) of base, stably.
32template <typename It, typename Less>
33void sym_merge(It base, std::size_t a, std::size_t m, std::size_t b, Less& less) {
34 if (m - a == 1) {
35 std::size_t lo = m, hi = b;
36 while (lo < hi) {
37 const std::size_t h = (lo + hi) / 2;
38 if (less(base[h], base[a]))
39 lo = h + 1;
40 else
41 hi = h;
42 }
43 for (std::size_t k = a; k + 1 < lo; ++k)
44 std::iter_swap(base + k, base + (k + 1));
45 return;
46 }
47 if (b - m == 1) {
48 std::size_t lo = a, hi = m;
49 while (lo < hi) {
50 const std::size_t h = (lo + hi) / 2;
51 if (!less(base[m], base[h]))
52 lo = h + 1;
53 else
54 hi = h;
55 }
56 for (std::size_t k = m; k > lo; --k)
57 std::iter_swap(base + k, base + (k - 1));
58 return;
59 }
60 const std::size_t mid = (a + b) / 2;
61 const std::size_t n = mid + m;
62 std::size_t start = m > mid ? n - b : a;
63 std::size_t r = m > mid ? mid : m;
64 const std::size_t p = n - 1;
65 while (start < r) {
66 const std::size_t c = (start + r) / 2;
67 if (!less(base[p - c], base[c]))
68 start = c + 1;
69 else
70 r = c;
71 }
72 const std::size_t end = n - start;
73 if (start < m && m < end)
74 std::rotate(base + start, base + m, base + end);
75 if (a < start && start < mid)
76 sym_merge(base, a, start, mid, less);
77 if (mid < end && end < b)
78 sym_merge(base, mid, end, b, less);
79}
80
81} // namespace stable_sort_detail
82
85template <typename It, typename Less> void stable_sort_in_place(It first, It last, Less less) {
86 const auto n = static_cast<std::size_t>(std::distance(first, last));
87 if (n < 2 || std::is_sorted(first, last, less))
88 return;
89 constexpr std::size_t kBlock = 20;
90 for (std::size_t a = 0; a < n; a += kBlock)
91 stable_sort_detail::insertion_sort(first, a, std::min(a + kBlock, n), less);
92 for (std::size_t width = kBlock; width < n; width *= 2)
93 for (std::size_t a = 0; a + width < n; a += 2 * width)
94 stable_sort_detail::sym_merge(first, a, a + width, std::min(a + 2 * width, n), less);
95}
96
102template <typename It, typename Key, typename Value, typename Count>
103bool stable_counting_sort(It first, It last, Key key, std::span<Value> scratch,
104 std::span<Count> counts) {
105 const auto n = static_cast<std::size_t>(std::distance(first, last));
106 if (n > scratch.size())
107 return false;
108 for (auto it = first; it != last; ++it) {
109 const auto k = key(*it);
110 if (k < 0 || static_cast<std::size_t>(k) >= counts.size())
111 return false;
112 }
113 std::fill(counts.begin(), counts.end(), Count{0});
114 for (auto it = first; it != last; ++it)
115 ++counts[static_cast<std::size_t>(key(*it))];
116 Count running = 0;
117 for (auto& count : counts) {
118 const auto here = count;
119 count = running;
120 running += here;
121 }
122 for (auto it = first; it != last; ++it)
123 scratch[counts[static_cast<std::size_t>(key(*it))]++] = *it;
124 std::copy(scratch.begin(), scratch.begin() + static_cast<std::ptrdiff_t>(n), first);
125 return true;
126}
127
128} // namespace pulp::state
Definition stable_sort.hpp:22
void insertion_sort(It base, std::size_t a, std::size_t b, Less &less)
Definition stable_sort.hpp:25
void sym_merge(It base, std::size_t a, std::size_t m, std::size_t b, Less &less)
Definition stable_sort.hpp:33
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
bool stable_counting_sort(It first, It last, Key key, std::span< Value > scratch, std::span< Count > counts)
Stable counting sort of [first, last) on a small non-negative integer key, O(n + counts....
Definition stable_sort.hpp:103
void stable_sort_in_place(It first, It last, Less less)
Sorts [first, last) by less, keeping equal elements in their original order, without allocating.
Definition stable_sort.hpp:85