Pixie
Loading...
Searching...
No Matches
compressed.h
Go to the documentation of this file.
1#pragma once
2
7
8#include <pixie/rank_select.h>
9#include <pixie/rank_select/detail/compressed/packed_prefix_search.h>
10#include <pixie/rank_select/detail/compressed/position_samples.h>
11#include <pixie/rank_select/detail/compressed/samples.h>
12#include <pixie/rank_select/detail/compressed/select_loaded_block.h>
13#include <pixie/rank_select/detail/compressed/select_metadata_search.h>
14#include <pixie/storage/aligned.h>
15
16#include <algorithm>
17#include <bit>
18#include <cassert>
19#include <cstddef>
20#include <cstdint>
21#include <cstring>
22#include <limits>
23#include <optional>
24#include <span>
25#include <stdexcept>
26#include <type_traits>
27#include <utility>
28#include <vector>
29
30namespace pixie {
71template <StorageImplementation MetadataStorage = AlignedStorage,
72 std::size_t SampleFrequency = 4096,
73 std::size_t SearchThreshold = 16>
75 : public RankSelectBase<RankSelectSupportCompressed<MetadataStorage,
76 SampleFrequency,
77 SearchThreshold>> {
78 static_assert(std::has_single_bit(SampleFrequency));
79 static_assert(SearchThreshold > 0);
80 static_assert(std::is_nothrow_move_constructible_v<MetadataStorage> &&
81 std::is_nothrow_move_assignable_v<MetadataStorage>,
82 "Compressed metadata storage must have nonthrowing moves");
83 static constexpr bool kNothrowDefaultConstruction =
84 std::is_nothrow_default_constructible_v<MetadataStorage>;
85
86 public:
88 enum class SelectSupport : std::uint8_t {
89 kNone = 0,
90 kSelect1 = 1,
91 kSelect0 = 2,
92 kBoth = 3,
93 };
94
97
100
104 kNothrowDefaultConstruction) {
105 swap_state(other);
106 }
107
113 const RankSelectSupportCompressed& other) {
114 if (this != &other) {
115 RankSelectSupportCompressed copy(other);
116 swap_state(copy);
117 }
118 return *this;
119 }
120
127 kNothrowDefaultConstruction) {
128 if (this != &other) {
129 RankSelectSupportCompressed moved(std::move(other));
130 swap_state(moved);
131 }
132 return *this;
133 }
134
148 std::span<const std::uint64_t> source_words,
149 std::size_t num_bits,
150 SelectSupport select_support = SelectSupport::kBoth)
152 : bits_(source_words),
153 num_bits_(num_bits),
154 select_support_(select_support) {
155 if (static_cast<std::uint8_t>(select_support) > 3) {
156 throw std::invalid_argument("Invalid compressed select support mode");
157 }
158 if (source_words.size() <= std::numeric_limits<std::size_t>::max() / 64) {
159 num_bits_ = std::min(num_bits_, source_words.size() * 64);
160 }
161 build_rank();
162 if (supports_select1_impl()) {
163 select1_ = build_samples<false>();
164 }
165 if (supports_select0_impl()) {
166 select0_ = build_samples<true>();
167 }
168 }
169
171 std::size_t size_impl() const { return num_bits_; }
172
174 int bit_impl(std::size_t position) const {
175 return (bits_[position / 64] >> (position % 64)) & 1;
176 }
177
179 // Inlining exposes block-aligned prefix calls in select's prediction path.
180#if __has_cpp_attribute(gnu::always_inline)
181 [[gnu::always_inline]]
182#endif
183 inline std::uint64_t rank_impl(std::size_t end) const {
184 if (end >= num_bits_) {
185 return one_count_;
186 }
187 const std::size_t super = end / kSuperBits;
188 const std::size_t basic = (end % kSuperBits) / kBasicBits;
189 const auto* record = rank_record(super);
190 std::uint64_t result = record[0] + local_rank(record, basic);
191 const std::size_t first_word = (end / kBasicBits) * 8;
192 const std::size_t offset = end % kBasicBits;
193 if (offset == 0) {
194 return result;
195 }
196 if (bits_.size() - first_word >= 8) {
197 return result + ::rank_512(bits_.data() + first_word, offset);
198 }
199 const std::size_t last_word = end / 64;
200 for (std::size_t word = first_word; word < last_word; ++word) {
201 result += std::popcount(bits_[word]);
202 }
203 if (end % 64 != 0) {
204 result += std::popcount(bits_[last_word] & ::first_bits_mask(end % 64));
205 }
206 return result;
207 }
208
210 std::uint64_t rank0_impl(std::size_t end) const {
211 if (end >= num_bits_) {
212 return num_bits_ - one_count_;
213 }
214 return end - rank_impl(end);
215 }
216
221 std::uint64_t select_impl(std::size_t rank) const {
222 return select_direction<false>(rank, select1_, supports_select1_impl());
223 }
224
229 std::uint64_t select0_impl(std::size_t rank) const {
230 return select_direction<true>(rank, select0_, supports_select0_impl());
231 }
232
235 return (static_cast<std::uint8_t>(select_support_) & 1) != 0;
236 }
237
240 return (static_cast<std::uint8_t>(select_support_) & 2) != 0;
241 }
242
244 std::size_t memory_usage_bytes_impl() const
245 requires requires(const MetadataStorage& storage) {
246 storage.allocated_bytes();
247 }
248 {
249 return sizeof(*this) + ranks_.allocated_bytes() +
250 select1_.allocated_bytes() + select0_.allocated_bytes();
251 }
252
253 private:
254 static constexpr std::size_t kSuperBits = 16384;
255 static constexpr std::size_t kBasicBits = 512;
256 static constexpr std::size_t kRecordBytes = 64;
257 // An interval contains at most ceil(SIZE_MAX / 2^14) superblocks, leaving
258 // the high size_t bit available for this contiguity marker.
259 static constexpr std::size_t kContiguous =
260 std::size_t{1} << (std::numeric_limits<std::size_t>::digits - 1);
261
262 struct WideEntry {
263 std::size_t begin;
264 std::size_t count;
265 };
266 using PrimarySamples = detail::compressed::BitmapMonotoneSamples<
267 detail::compressed::BlockedMonotoneSamples<MetadataStorage>>;
268
269 using PositionSamples = detail::compressed::PositionSamples<MetadataStorage>;
270 template <class T>
271 using MetadataArray = detail::compressed::MetadataArray<T, MetadataStorage>;
272
273 struct SelectIndex {
274 enum class Shape : std::uint8_t {
275 kMixed,
276 kNarrow,
277 kWidePrefix,
278 kHomogeneous,
279 };
280 using Endpoint = std::conditional_t<
281 (SampleFrequency - 1 <= std::numeric_limits<std::uint16_t>::max()),
282 std::uint16_t,
283 std::conditional_t<(SampleFrequency - 1 <=
284 std::numeric_limits<std::uint32_t>::max()),
285 std::uint32_t,
286 std::uint64_t>>;
287 PrimarySamples samples;
288 PositionSamples positions;
289 bool predicted = false;
290 detail::compressed::BlockedMonotoneSamples<MetadataStorage> occupied;
291 MetadataArray<WideEntry> wide;
292 MetadataArray<Endpoint> endpoints;
293 Shape shape{};
294
295 std::size_t allocated_bytes() const {
296 return samples.allocated_bytes() + positions.allocated_bytes() +
297 occupied.allocated_bytes() + wide.allocated_bytes() +
298 endpoints.allocated_bytes();
299 }
300 };
301
302 std::span<const std::uint64_t> bits_;
303 std::size_t num_bits_ = 0;
304 std::uint64_t one_count_ = 0;
305 MetadataArray<std::uint64_t> ranks_;
306 SelectIndex select1_;
307 SelectIndex select0_;
308 SelectSupport select_support_ = SelectSupport::kNone;
309
310 void swap_state(RankSelectSupportCompressed& other) noexcept {
311 using std::swap;
312 swap(bits_, other.bits_);
313 swap(num_bits_, other.num_bits_);
314 swap(one_count_, other.one_count_);
315 swap(ranks_, other.ranks_);
316 swap(select1_, other.select1_);
317 swap(select0_, other.select0_);
318 swap(select_support_, other.select_support_);
319 }
320
321 std::size_t super_count() const { return ranks_.size() / (kRecordBytes / 8); }
322
323 const std::uint64_t* rank_record(std::size_t super) const {
324 return ranks_.data() + super * (kRecordBytes / 8);
325 }
326
327 static std::uint64_t local_rank(const std::uint64_t* record,
328 std::size_t blocks) {
329 if (blocks == 0) {
330 return 0;
331 }
332
333 if (blocks == 32) {
334 const auto terminal = record[7] >> 50;
335 // With a zero terminal encoding, the first prefix is exactly 0 or
336 // 512; shifting it reconstructs 0 or 16384 without a second test.
337 return terminal != 0 ? terminal : (record[1] & 0x3fff) << 5;
338 }
339 const std::size_t bit = (blocks - 1) * 14;
340 constexpr auto mask = 0x3fffu;
341 if constexpr (std::endian::native == std::endian::little) {
342 std::uint32_t packed;
343 // The largest byte offset is 52. A four-byte load stays inside the seven
344 // packed words even at the final nonterminal prefix (block 31).
345 std::memcpy(&packed,
346 reinterpret_cast<const std::byte*>(record + 1) + bit / 8,
347 sizeof(packed));
348 return (packed >> (bit % 8)) & mask;
349 } else {
350 auto packed = record[1 + bit / 64] >> (bit % 64);
351 constexpr unsigned width = 14;
352 if (bit % 64 + width > 64) {
353 packed |= record[2 + bit / 64] << (64 - bit % 64);
354 }
355 return packed & mask;
356 }
357 }
358
359 static void write_local_rank(std::uint64_t* record,
360 std::size_t blocks,
361 std::uint64_t value) {
362 const std::size_t bit = (blocks - 1) * 14;
363 constexpr unsigned width = 14;
364 value &= 0x3fff;
365 record[1 + bit / 64] |= value << (bit % 64);
366 if (bit % 64 + width > 64) {
367 record[2 + bit / 64] |= value >> (64 - bit % 64);
368 }
369 }
370
371 std::size_t super_size(std::size_t super) const {
372 return std::min(kSuperBits, num_bits_ - super * kSuperBits);
373 }
374
375 void build_rank() {
376 const std::size_t count =
377 num_bits_ == 0 ? 0 : 1 + (num_bits_ - 1) / kSuperBits;
378 ranks_ = MetadataArray<std::uint64_t>(count * (kRecordBytes / 8));
379 auto* words = ranks_.data();
380 for (std::size_t super = 0; super < count; ++super) {
381 auto* record = words + super * (kRecordBytes / 8);
382 record[0] = one_count_;
383 const std::size_t base = super * kSuperBits;
384 const std::size_t valid = super_size(super);
385 std::uint64_t local = 0;
386
387 for (std::size_t basic = 0; basic < 32; ++basic) {
388 for (std::size_t lane = 0; lane < 8; ++lane) {
389 const std::size_t offset = basic * kBasicBits + lane * 64;
390 if (offset >= valid) {
391 break;
392 }
393 const auto word = bits_[base / 64 + offset / 64];
394 local += std::popcount(word & ::first_bits_mask(std::min<std::size_t>(
395 64, valid - offset)));
396 }
397
398 write_local_rank(record, basic + 1, local);
399 }
400
401 one_count_ += local;
402 }
403 }
404
405 template <bool Zero>
406 std::uint64_t prefix_before(std::size_t super) const {
407 const std::uint64_t ones = rank_record(super)[0];
408 if constexpr (Zero) {
409 return super * kSuperBits - ones;
410 }
411 return ones;
412 }
413
414 template <bool Zero>
415 std::uint64_t prefix_after(std::size_t super) const {
416 const auto* record = rank_record(super);
417 const std::uint64_t ones = record[0] + local_rank(record, 32);
418 if constexpr (Zero) {
419 return super * kSuperBits + super_size(super) - ones;
420 }
421 return ones;
422 }
423
424 template <bool Zero>
425 SelectIndex build_samples() const {
426 const std::uint64_t total = Zero ? num_bits_ - one_count_ : one_count_;
427 if (total == 0) {
428 return {};
429 }
430
431 if (num_bits_ <= 1024) {
432 // All queries fit one superblock and at most two basic blocks.
433 // The first local prefix supplies the complete localization boundary.
434 return {};
435 }
436
437 const std::size_t sample_count = 1 + (total - 1) / SampleFrequency;
438 std::vector<std::uint64_t> samples;
439 if (sample_count == std::numeric_limits<std::size_t>::max()) {
440 throw std::length_error("Too many select samples");
441 }
442 samples.reserve(sample_count + 1);
443 std::size_t last_occupied = 0;
444 [[maybe_unused]] bool homogeneous = true;
445 for (std::size_t super = 0; super < super_count(); ++super) {
446 const auto end = prefix_after<Zero>(super);
447 const auto before = prefix_before<Zero>(super);
448 if constexpr (SampleFrequency <= kSuperBits) {
449 homogeneous &= end == before || end - before == super_size(super);
450 }
451 if (end != before) {
452 last_occupied = super;
453 }
454 while (samples.size() < sample_count &&
455 samples.size() * SampleFrequency < end) {
456 samples.push_back(super);
457 }
458 }
459 samples.push_back(last_occupied + 1);
460
461 SelectIndex result;
462 if constexpr (SampleFrequency <= kSuperBits) {
463 if (homogeneous) {
464 result.samples = PrimarySamples(samples);
465 result.shape = SelectIndex::Shape::kHomogeneous;
466 return result;
467 }
468 }
469 std::vector<std::uint64_t> occupied;
470 std::vector<std::uint64_t> wide_samples;
471 std::vector<WideEntry> wide;
472 std::vector<typename SelectIndex::Endpoint> endpoints;
473 bool needs_occupied_ids = false;
474 for (std::size_t sample = 0; sample < sample_count; ++sample) {
475 if (samples[sample + 1] - samples[sample] < SearchThreshold) {
476 continue;
477 }
478 const std::uint64_t begin_rank = sample * SampleFrequency;
479 const std::uint64_t end_rank =
480 begin_rank +
481 std::min<std::uint64_t>(SampleFrequency, total - begin_rank);
482 const std::size_t begin = occupied.size();
483 const std::size_t last =
484 std::min<std::size_t>(samples[sample + 1], super_count() - 1);
485 for (std::size_t super = samples[sample]; super <= last; ++super) {
486 const auto before = prefix_before<Zero>(super);
487 const auto after = prefix_after<Zero>(super);
488 if (after > before && after > begin_rank && before < end_rank) {
489 occupied.push_back(super);
490 endpoints.push_back(static_cast<typename SelectIndex::Endpoint>(
491 std::min<std::uint64_t>(after - begin_rank, SampleFrequency) -
492 1));
493 }
494 }
495 const std::size_t count = occupied.size() - begin;
496 const bool contiguous = occupied.back() - occupied[begin] + 1 == count;
497 needs_occupied_ids |= !contiguous;
498
499 wide.push_back({begin, count | (contiguous ? kContiguous : 0)});
500 wide_samples.push_back(sample);
501 }
502 if (needs_occupied_ids) {
503 result.occupied =
504 detail::compressed::BlockedMonotoneSamples<MetadataStorage>(occupied);
505 }
506 result.wide = MetadataArray<WideEntry>(wide);
507 result.endpoints = MetadataArray<typename SelectIndex::Endpoint>(endpoints);
508
509 const auto last_wide = wide_samples.empty() ? 0 : wide_samples.back();
510 result.shape = result.wide.empty() ? SelectIndex::Shape::kNarrow
511 : last_wide == result.wide.size() - 1
512 ? SelectIndex::Shape::kWidePrefix
513 : SelectIndex::Shape::kMixed;
514
515 if (result.shape != SelectIndex::Shape::kMixed) {
516 wide_samples.clear();
517 }
518
519 if (one_count_ >= num_bits_ / 16 &&
520 num_bits_ - one_count_ >= num_bits_ / 16) {
521 std::vector<std::uint64_t> positions;
522 positions.reserve(sample_count + 1);
523 for (std::size_t sample = 0; sample < sample_count; ++sample) {
524 positions.push_back(select_in_super<Zero>(sample * SampleFrequency + 1,
525 samples[sample]));
526 }
527 // Use one past the last selected position, excluding trailing empty bits.
528 positions.push_back(select_in_super<Zero>(total, last_occupied) + 1);
529 result.positions = PositionSamples(positions, wide_samples);
530 result.predicted = true;
531 } else {
532 result.samples = PrimarySamples(samples, wide_samples);
533 }
534
535 return result;
536 }
537
538 template <bool Zero>
539 std::uint64_t select_direction(std::size_t rank,
540 const SelectIndex& index,
541 bool enabled) const {
542 if (rank == 0) {
543 return 0;
544 }
545 const std::uint64_t total = Zero ? num_bits_ - one_count_ : one_count_;
546 if (!enabled || rank > total) {
547 return num_bits_;
548 }
549
550 if (num_bits_ <= 1024) {
551 return total == num_bits_ ? rank - 1
552 : select_small_index<Zero>(rank, total);
553 }
554
555 const std::size_t sample = (rank - 1) / SampleFrequency;
556 if constexpr (SampleFrequency <= kSuperBits) {
557 if (index.shape == SelectIndex::Shape::kHomogeneous) {
558 // Sampling divides each full occupied superblock's population, so
559 // the local offset follows arithmetically without reading rank
560 // metadata.
561 const auto super = index.samples[sample];
562 return super * kSuperBits + ((rank - 1) & (kSuperBits - 1));
563 }
564 }
565 std::uint64_t predicted_first = 0;
566 if (index.predicted) {
567 const auto [first, next] = index.positions.bounds(sample);
568 predicted_first = first / kSuperBits;
569 if (const auto answer = predict<Zero>(rank, first, next)) {
570 return *answer;
571 }
572 }
573 const WideEntry* entry = nullptr;
574 switch (index.shape) {
575 case SelectIndex::Shape::kNarrow:
576 break;
577 case SelectIndex::Shape::kWidePrefix:
578 if (sample < index.wide.size()) {
579 entry = &index.wide[sample];
580 }
581 break;
582 case SelectIndex::Shape::kMixed:
583 if (const auto mapped = index.predicted
584 ? index.positions.wide_entry(sample)
585 : index.samples.wide_entry(sample)) {
586 entry = &index.wide[*mapped];
587 }
588 break;
589 case SelectIndex::Shape::kHomogeneous:
590 // The arithmetic path above handles all homogeneous indexes.
591 assert(false);
592 break;
593 }
594
595 std::size_t super;
596 if (entry == nullptr) {
597 super = index.predicted ? predicted_first : index.samples[sample];
598 while (prefix_after<Zero>(super) < rank) {
599 ++super;
600 }
601 } else {
602 const std::size_t count = entry->count & ~kContiguous;
603 const auto relative_rank = (rank - 1) % SampleFrequency;
604 // A singleton interval identifies the superblock without an endpoint
605 // load.
606 const std::size_t offset =
607 count == 1 ? 0
608 : detail::compressed::grouped_lower_bound(
609 index.endpoints.data() + entry->begin, count,
610 relative_rank);
611 assert(offset < count);
612 if ((entry->count & kContiguous) != 0) {
613 super = (index.predicted ? predicted_first : index.samples[sample]) +
614 offset;
615 } else {
616 super = index.occupied[entry->begin + offset];
617 }
618 }
619
620 return select_in_super<Zero>(rank, super);
621 }
622
623 template <bool Zero>
624 std::uint64_t select_in_super(std::size_t rank, std::size_t super) const {
625 const auto* record = rank_record(super);
626 const std::uint64_t target = rank - prefix_before<Zero>(super);
627 const std::size_t valid = super_size(super);
628 const auto ones_local = local_rank(record, 32);
629 const std::uint64_t total_local = Zero ? valid - ones_local : ones_local;
630 if (total_local == valid) {
631 return super * kSuperBits + target - 1;
632 }
633 const std::size_t blocks = 1 + (valid - 1) / kBasicBits;
634 const auto packed_location =
635 detail::compressed::packed_prefix_search<Zero>(record, target, valid);
636
637 std::size_t basic;
638 std::uint64_t before;
639 std::uint64_t after;
640 if (packed_location) {
641 basic = packed_location->basic;
642 before = packed_location->before;
643 after = packed_location->after;
644 } else {
645 basic = (target - 1) * blocks / total_local;
646 const auto count_at = [&](std::size_t boundary) {
647 const std::uint64_t ones = local_rank(record, boundary);
648 return Zero ? std::min(boundary * kBasicBits, valid) - ones : ones;
649 };
650 // The estimate is only a starting point. Exact corrections also handle
651 // arbitrary clustering and repeated local counts within this cache line.
652 before = count_at(basic);
653 after = before;
654 if (before >= target) {
655 do {
656 after = before;
657 before = count_at(--basic);
658 } while (before >= target);
659 } else {
660 after = count_at(basic + 1);
661 while (after < target) {
662 before = after;
663 after = count_at(++basic + 1);
664 }
665 }
666 }
667 const std::size_t base = super * kSuperBits + basic * kBasicBits;
668 return select_basic<Zero>(base, target - before, after - before);
669 }
670
671 template <bool Zero>
672 std::uint64_t basic_prefix(std::size_t end) const {
673 if constexpr (Zero) {
674 return rank0_impl(end);
675 } else {
676 return rank_impl(end);
677 }
678 }
679
680 template <bool Zero>
681 std::optional<std::uint64_t> predict(std::size_t rank,
682 std::uint64_t first,
683 std::uint64_t next) const {
684 const auto span = next - first;
685 if (span > 65536) {
686 return std::nullopt;
687 }
688 const auto guess =
689 first + static_cast<std::uint64_t>(static_cast<unsigned __int128>(
690 (rank - 1) % SampleFrequency) *
691 span / SampleFrequency);
692 const std::size_t base = (guess / kBasicBits) * kBasicBits;
693 if (num_bits_ - base < kBasicBits) {
694 return std::nullopt;
695 }
696 __builtin_prefetch(bits_.data() + base / 64, 0, 3);
697 const auto before = basic_prefix<Zero>(base);
698 const auto after = basic_prefix<Zero>(base + kBasicBits);
699 if (before < rank && rank <= after) {
700 return select_basic<Zero>(base, rank - before, after - before);
701 }
702 if (rank <= before && base >= kBasicBits) {
703 const auto lower = basic_prefix<Zero>(base - kBasicBits);
704 if (lower < rank) {
705 return select_basic<Zero>(base - kBasicBits, rank - lower,
706 before - lower);
707 }
708 } else if (rank > after && num_bits_ - base >= 2 * kBasicBits) {
709 const auto upper = basic_prefix<Zero>(base + 2 * kBasicBits);
710 if (rank <= upper) {
711 return select_basic<Zero>(base + kBasicBits, rank - after,
712 upper - after);
713 }
714 }
715 return std::nullopt;
716 }
717
718 template <bool Zero>
719#if __has_cpp_attribute(gnu::noinline)
720 [[gnu::noinline]]
721#endif
722 std::uint64_t select_small_index(std::size_t rank,
723 std::uint64_t total) const {
724 const std::uint64_t first_ones = local_rank(rank_record(0), 1);
725 const std::uint64_t first_end =
726 Zero ? std::min(kBasicBits, num_bits_) - first_ones : first_ones;
727 const std::size_t basic = rank > first_end;
728 const std::uint64_t before = basic ? first_end : 0;
729 const std::uint64_t after = basic ? total : first_end;
730 return select_basic<Zero>(basic * kBasicBits, rank - before,
731 after - before);
732 }
733
734 template <bool Zero>
735#if __has_cpp_attribute(gnu::always_inline)
736 [[gnu::always_inline]]
737#endif
738 inline std::uint64_t select_basic(std::size_t base,
739 std::uint64_t remaining,
740 std::uint64_t population) const {
741 if (population == kBasicBits) {
742 return base + remaining - 1;
743 }
744 if (num_bits_ - base >= kBasicBits) {
745 return base + detail::compressed::select_loaded_block<Zero>(
746 bits_.data() + base / 64, remaining - 1);
747 }
748 for (std::size_t lane = 0; lane < 8; ++lane) {
749 const std::size_t position = base + lane * 64;
750 if (position >= num_bits_) {
751 break;
752 }
753 std::uint64_t word = bits_[position / 64];
754 if constexpr (Zero) {
755 word = ~word;
756 }
757 word &=
758 ::first_bits_mask(std::min<std::size_t>(64, num_bits_ - position));
759 const unsigned count = std::popcount(word);
760 if (remaining <= count) {
761 return position + ::select_64(word, remaining - 1);
762 }
763 remaining -= count;
764 }
765 assert(false && "Select localization must contain the target");
766 return num_bits_;
767 }
768};
769
770} // namespace pixie
Owning storage with a logical byte size and 64-byte-aligned backing.
Definition aligned.h:41
CRTP facade for immutable rank/select queries and bit access.
Definition rank_select.h:32
std::uint64_t rank(std::size_t end_position) const
Definition rank_select.h:66
Non-owning rank/select index with compressed interleaved metadata.
Definition compressed.h:77
SelectSupport
Optional select directions; rank is always indexed.
Definition compressed.h:88
std::uint64_t rank0_impl(std::size_t end) const
Count zeros in [0, end), clamping to the logical bit length.
Definition compressed.h:210
std::uint64_t select_impl(std::size_t rank) const
Locate a one-based one occurrence as a zero-based bit position.
Definition compressed.h:221
RankSelectSupportCompressed(std::span< const std::uint64_t > source_words, std::size_t num_bits, SelectSupport select_support=SelectSupport::kBoth)
Index caller-owned packed words, ignoring padding after valid bits.
Definition compressed.h:147
std::size_t memory_usage_bytes_impl() const
Object plus allocated auxiliary bytes, excluding borrowed words.
Definition compressed.h:244
int bit_impl(std::size_t position) const
Read a zero-based bit; position must be below size().
Definition compressed.h:174
RankSelectSupportCompressed & operator=(const RankSelectSupportCompressed &other)
Duplicate metadata while borrowing the assigned source.
Definition compressed.h:112
RankSelectSupportCompressed(const RankSelectSupportCompressed &)=default
Duplicate metadata while borrowing the same immutable source.
std::uint64_t rank_impl(std::size_t end) const
Count ones in [0, end), clamping end at size().
Definition compressed.h:183
std::size_t size_impl() const
Return the logical source bit count.
Definition compressed.h:171
RankSelectSupportCompressed()=default
Construct an empty index with no select directions enabled.
RankSelectSupportCompressed & operator=(RankSelectSupportCompressed &&other) noexcept(kNothrowDefaultConstruction)
Transfer metadata, leaving the source empty with select disabled.
Definition compressed.h:126
bool supports_select0_impl() const
Report whether zero-select metadata was requested.
Definition compressed.h:239
std::uint64_t select0_impl(std::size_t rank) const
Locate a one-based zero occurrence as a zero-based bit position.
Definition compressed.h:229
bool supports_select1_impl() const
Report whether one-select metadata was requested.
Definition compressed.h:234
RankSelectSupportCompressed(RankSelectSupportCompressed &&other) noexcept(kNothrowDefaultConstruction)
Transfer metadata, leaving the source empty with select disabled.
Definition compressed.h:103
A concrete CRTP implementation of StorageBase.
Definition storage.h:290
Common interface for rank/select support over packed bit sequences.