Pixie
Loading...
Searching...
No Matches
sparse_table.h
1#pragma once
2
3#include <pixie/memory_usage.h>
4#include <pixie/rmq.h>
5#include <pixie/storage/aligned.h>
6
7#include <algorithm>
8#include <bit>
9#include <cstddef>
10#include <functional>
11#include <limits>
12#include <memory>
13#include <new>
14#include <numeric>
15#include <span>
16#include <stdexcept>
17#include <vector>
18
19namespace pixie::rmq {
20
34template <class T,
35 class Compare = std::less<T>,
36 class Index = std::size_t,
37 std::size_t Alignment = pixie::kAlignedStorageLineBytes>
39 : public RmqBase<SparseTable<T, Compare, Index, Alignment>, T> {
40 public:
41 static constexpr std::size_t npos =
43 static constexpr Index invalid_index = std::numeric_limits<Index>::max();
44
48 SparseTable() = default;
49
60 explicit SparseTable(std::span<const T> values, Compare compare = Compare())
61 : values_(values), compare_(compare) {
62 build();
63 }
64
70 std::size_t size_impl() const { return values_.size(); }
71
78 T value_at_impl(std::size_t position) const { return values_[position]; }
79
90 std::size_t arg_min_impl(std::size_t left, std::size_t right) const {
91 if (left >= right || right > values_.size()) {
92 return npos;
93 }
94 const std::size_t length = right - left;
95 const std::size_t level = std::bit_width(length) - 1;
96 const std::size_t span = std::size_t{1} << level;
97 const std::size_t first = table_[level][left];
98 const std::size_t second = table_[level][right - span];
99 return better(first, second);
100 }
101
108 std::size_t memory_usage_bytes_impl() const {
109 std::size_t bytes = sizeof(*this);
110 bytes += pixie::vector_capacity_bytes(table_);
111 for (const TableLevel& level : table_) {
112 bytes += pixie::vector_capacity_bytes(level);
113 }
114 return bytes;
115 }
116
117 private:
128 std::size_t better(std::size_t left, std::size_t right) const {
129 if (compare_(values_[right], values_[left])) {
130 return right;
131 }
132 if (compare_(values_[left], values_[right])) {
133 return left;
134 }
135 return std::min(left, right);
136 }
137
149 Index build_better(Index left, Index right) const {
150 return compare_(values_[right], values_[left]) ? right : left;
151 }
152
161 void build() {
162 table_.clear();
163 if (values_.empty()) {
164 return;
165 }
166 if (values_.size() > static_cast<std::size_t>(invalid_index)) {
167 throw std::length_error("RMQ sparse table index type is too small");
168 }
169
170 table_.reserve(std::bit_width(values_.size()));
171 table_.emplace_back(values_.size());
172 std::iota(table_[0].begin(), table_[0].end(), Index{0});
173
174 for (std::size_t span = 2, half = 1; span <= values_.size();
175 half = span, span <<= 1) {
176 const std::size_t level = table_.size();
177 table_.emplace_back(values_.size() - span + 1);
178 for (std::size_t i = 0; i < table_[level].size(); ++i) {
179 table_[level][i] = static_cast<Index>(
180 build_better(table_[level - 1][i], table_[level - 1][i + half]));
181 }
182 }
183 }
184
185 template <class Value, std::size_t AllocationAlignment>
186 class AlignedAllocator {
187 public:
188 static_assert(AllocationAlignment >= alignof(Value));
189 static_assert((AllocationAlignment & (AllocationAlignment - 1)) == 0);
190
191 using value_type = Value;
192
193 AlignedAllocator() = default;
194
195 template <class Other>
196 AlignedAllocator(
197 const AlignedAllocator<Other, AllocationAlignment>&) noexcept {}
198
199 [[nodiscard]] Value* allocate(std::size_t count) {
200 if (count == 0) {
201 return nullptr;
202 }
203 if (count > std::numeric_limits<std::size_t>::max() / sizeof(Value)) {
204 throw std::bad_array_new_length();
205 }
206 return static_cast<Value*>(::operator new(
207 count * sizeof(Value), std::align_val_t{AllocationAlignment}));
208 }
209
210 void deallocate(Value* pointer, std::size_t) noexcept {
211 ::operator delete(pointer, std::align_val_t{AllocationAlignment});
212 }
213
214 template <class Other>
215 bool operator==(
216 const AlignedAllocator<Other, AllocationAlignment>&) const noexcept {
217 return true;
218 }
219
220 template <class Other>
221 bool operator!=(
222 const AlignedAllocator<Other, AllocationAlignment>&) const noexcept {
223 return false;
224 }
225
226 template <class Other>
227 struct rebind {
228 using other = AlignedAllocator<Other, AllocationAlignment>;
229 };
230 };
231
232 template <class Value, std::size_t AllocationAlignment>
233 struct LevelAllocator {
234 using type = AlignedAllocator<Value, AllocationAlignment>;
235 };
236
237 template <class Value>
238 struct LevelAllocator<Value, 0> {
239 using type = std::allocator<Value>;
240 };
241
242 using TableLevel =
243 std::vector<Index, typename LevelAllocator<Index, Alignment>::type>;
244
245 std::span<const T> values_;
246 Compare compare_;
247 std::vector<TableLevel> table_;
248};
249
250} // namespace pixie::rmq
CRTP facade for static range-minimum-query indexes.
Definition rmq.h:28
T value_at_impl(std::size_t position) const
Return the value at an indexed position.
Definition sparse_table.h:78
SparseTable()=default
Construct an empty sparse table.
SparseTable(std::span< const T > values, Compare compare=Compare())
Build a sparse table over values.
Definition sparse_table.h:60
std::size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition sparse_table.h:108
std::size_t arg_min_impl(std::size_t left, std::size_t right) const
Return the first minimum position in [left, right).
Definition sparse_table.h:90
std::size_t size_impl() const
Return the number of indexed values.
Definition sparse_table.h:70
Definition rmq.h:15
Common interface for static range-minimum-query indexes.