Pixie
Loading...
Searching...
No Matches
bitvector.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/cache_line.h>
5
6#include <algorithm>
7#include <bit>
8#include <cstdint>
9#include <span>
10#include <string>
11#include <vector>
12
13#ifdef PIXIE_DIAGNOSTICS
14#include <spdlog/spdlog.h>
15#endif
16
17namespace pixie {
18
63class BitVector {
64 private:
65 constexpr static size_t kWordSize = 64;
66 constexpr static size_t kSuperBlockRankIntSize = 64;
67 constexpr static size_t kBasicBlockRankIntSize = 16;
68 constexpr static size_t kBasicBlockSize = 512;
69 constexpr static size_t kWordsPerBlock = 8;
70 constexpr static size_t kSuperBlockSize = 65536;
71 constexpr static size_t kBlocksPerSuperBlock = 128;
72 constexpr static size_t kSelectSampleFrequency = 16384;
73
74 alignas(64) uint64_t delta_super[8];
75 alignas(64) uint16_t delta_basic[32];
76
77 AlignedStorage super_block_rank_; // 64-bit global prefix sums
78 AlignedStorage basic_block_rank_; // 16-bit local prefix sums
79 AlignedStorage select1_samples_; // 64-bit global positions
80 AlignedStorage select0_samples_; // 64-bit global positions
81 size_t num_bits_{};
82 size_t padded_size_{};
83 size_t max_rank_{};
84
85 std::span<const uint64_t> bits_;
86
87 size_t logical_word_count() const {
88 return (num_bits_ + kWordSize - 1) / kWordSize;
89 }
90
91 size_t logical_word_bits(size_t word_index) const {
92 const size_t begin = word_index * kWordSize;
93 if (begin >= num_bits_) {
94 return 0;
95 }
96 return std::min(kWordSize, num_bits_ - begin);
97 }
98
99 uint64_t logical_word(size_t word_index) const {
100 if (word_index >= bits_.size()) {
101 return 0;
102 }
103 const size_t bits = logical_word_bits(word_index);
104 if (bits == 0) {
105 return 0;
106 }
107 if (bits == kWordSize) {
108 return bits_[word_index];
109 }
110 return bits_[word_index] & first_bits_mask(bits);
111 }
112
113 uint64_t rank_in_basic_block(size_t basic_block, size_t offset) const {
114 if (offset == 0) {
115 return 0;
116 }
117 const size_t first_word = basic_block * kWordsPerBlock;
118 if (first_word + kWordsPerBlock <= bits_.size()) {
119 return rank_512(&bits_[first_word], offset);
120 }
121
122 uint64_t result = 0;
123 size_t word_index = first_word;
124 while (offset >= kWordSize) {
125 result += std::popcount(logical_word(word_index));
126 offset -= kWordSize;
127 ++word_index;
128 }
129 if (offset != 0) {
130 result +=
131 std::popcount(logical_word(word_index) & first_bits_mask(offset));
132 }
133 return result;
134 }
135
136 uint64_t select_in_words(size_t first_word, size_t rank, bool value) const {
137 const size_t first_bit = first_word * kWordSize;
138 if (first_bit + kBasicBlockSize <= num_bits_ &&
139 first_word + kWordsPerBlock <= bits_.size()) {
140 return value ? first_bit + select_512(&bits_[first_word], rank - 1)
141 : first_bit + select0_512(&bits_[first_word], rank - 1);
142 }
143
144 for (size_t word_index = first_word; word_index < logical_word_count();
145 ++word_index) {
146 const uint64_t word = logical_word(word_index);
147 const uint64_t candidates =
148 value ? word
149 : (~word & first_bits_mask(logical_word_bits(word_index)));
150 const size_t count = std::popcount(candidates);
151 if (rank > count) {
152 rank -= count;
153 continue;
154 }
155 return word_index * kWordSize + select_64(candidates, rank - 1);
156 }
157 return num_bits_;
158 }
159
163 void build_rank() {
164 size_t num_superblocks =
165 8 + (padded_size_ == 0 ? 0 : (padded_size_ - 1) / kSuperBlockSize);
166 // Add more blocks to ease SIMD processing
167 // num_basicblocks to fully cover superblock, i.e. 128
168 // This reduces branching in select
169 num_superblocks = ((num_superblocks + 7) / 8) * 8;
170 size_t num_basicblocks = num_superblocks * kBlocksPerSuperBlock;
171 super_block_rank_.resize(num_superblocks * 64);
172 basic_block_rank_.resize(num_basicblocks * 16);
173
174 auto super_block_rank = super_block_rank_.As64BitInts();
175 auto basic_block_rank = basic_block_rank_.As16BitInts();
176
177 uint64_t super_block_sum = 0;
178 uint64_t basic_block_sum = 0;
179
180 for (size_t i = 0; i / kBasicBlockSize < basic_block_rank.size();
181 i += kWordSize) {
182 if (i % kSuperBlockSize == 0) {
183 super_block_sum += basic_block_sum;
184 super_block_rank[i / kSuperBlockSize] = super_block_sum;
185 basic_block_sum = 0;
186 }
187 if (i % kBasicBlockSize == 0) {
188 basic_block_rank[i / kBasicBlockSize] =
189 static_cast<uint16_t>(basic_block_sum);
190 }
191 if (i / kWordSize < logical_word_count()) {
192 basic_block_sum += std::popcount(logical_word(i / kWordSize));
193 }
194 }
195 max_rank_ = super_block_sum + basic_block_sum;
196 }
197
201 void build_select() {
202 uint64_t milestone = kSelectSampleFrequency;
203 uint64_t milestone0 = kSelectSampleFrequency;
204 uint64_t rank = 0;
205 uint64_t rank0 = 0;
206
207 size_t num_one_samples =
208 1 + (max_rank_ + kSelectSampleFrequency - 1) / kSelectSampleFrequency;
209 size_t num_zero_samples =
210 1 + (num_bits_ - max_rank_ + kSelectSampleFrequency - 1) /
211 kSelectSampleFrequency;
212
213 select1_samples_.resize(num_one_samples * 64);
214 select0_samples_.resize(num_zero_samples * 64);
215 auto select1_samples = select1_samples_.As64BitInts();
216 auto select0_samples = select0_samples_.As64BitInts();
217
218 select1_samples[0] = 0;
219 select0_samples[0] = 0;
220
221 size_t num_zeros = 1, num_ones = 1;
222
223 for (size_t i = 0; i < logical_word_count(); ++i) {
224 const uint64_t word = logical_word(i);
225 const auto ones = std::popcount(word);
226 const auto zeros = logical_word_bits(i) - ones;
227 if (rank + ones >= milestone) {
228 auto pos = select_64(word, milestone - rank - 1);
229 // TODO: try including global rank into select samples to save
230 // a cache miss on global rank scan
231 select1_samples[num_ones++] = (64 * i + pos) / kSuperBlockSize;
232 milestone += kSelectSampleFrequency;
233 }
234 if (rank0 + zeros >= milestone0) {
235 const uint64_t zero_word =
236 ~word & first_bits_mask(logical_word_bits(i));
237 auto pos = select_64(zero_word, milestone0 - rank0 - 1);
238 select0_samples[num_zeros++] = (64 * i + pos) / kSuperBlockSize;
239 milestone0 += kSelectSampleFrequency;
240 }
241 rank += ones;
242 rank0 += zeros;
243 }
244
245 for (size_t i = 0; i < 8; ++i) {
246 delta_super[i] = i * kSuperBlockSize;
247 }
248 for (size_t i = 0; i < 32; ++i) {
249 delta_basic[i] = i * kBasicBlockSize;
250 }
251 }
252
258 uint64_t find_superblock(uint64_t rank) const {
259 auto select1_samples = select1_samples_.AsConst64BitInts();
260 auto super_block_rank = super_block_rank_.AsConst64BitInts();
261
262 uint64_t left = select1_samples[rank / kSelectSampleFrequency];
263
264 while (left + 7 < super_block_rank.size()) {
265 auto len = lower_bound_8x64(&super_block_rank[left], rank);
266 if (len < 8) {
267 return left + len - 1;
268 }
269 left += 8;
270 }
271 if (left + 3 < super_block_rank.size()) {
272 auto len = lower_bound_4x64(&super_block_rank[left], rank);
273 if (len < 4) {
274 return left + len - 1;
275 }
276 left += 4;
277 }
278 while (left < super_block_rank.size() && super_block_rank[left] < rank) {
279 left++;
280 }
281 return left - 1;
282 }
283
289 uint64_t find_superblock_zeros(uint64_t rank0) const {
290 auto select0_samples = select0_samples_.AsConst64BitInts();
291 auto super_block_rank = super_block_rank_.AsConst64BitInts();
292
293 uint64_t left = select0_samples[rank0 / kSelectSampleFrequency];
294
295 while (left + 7 < super_block_rank.size()) {
296 auto len = lower_bound_delta_8x64(&super_block_rank[left], rank0,
297 delta_super, kSuperBlockSize * left);
298 if (len < 8) {
299 return left + len - 1;
300 }
301 left += 8;
302 }
303 if (left + 3 < super_block_rank.size()) {
304 auto len = lower_bound_delta_4x64(&super_block_rank[left], rank0,
305 delta_super, kSuperBlockSize * left);
306 if (len < 4) {
307 return left + len - 1;
308 }
309 left += 4;
310 }
311 while (left < super_block_rank.size() &&
312 kSuperBlockSize * left - super_block_rank[left] < rank0) {
313 left++;
314 }
315 return left - 1;
316 }
317
329 uint64_t find_basicblock(uint16_t local_rank, uint64_t s_block) const {
330 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
331
332 for (size_t pos = 0; pos < kBlocksPerSuperBlock; pos += 32) {
333 auto count = lower_bound_32x16(
334 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
335 if (count < 32) {
336 return kBlocksPerSuperBlock * s_block + pos + count - 1;
337 }
338 }
339 return kBlocksPerSuperBlock * s_block + kBlocksPerSuperBlock - 1;
340 }
341
353 uint64_t find_basicblock_zeros(uint16_t local_rank0, uint64_t s_block) const {
354 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
355 for (size_t pos = 0; pos < kBlocksPerSuperBlock; pos += 32) {
356 auto count = lower_bound_delta_32x16(
357 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
358 delta_basic, kBasicBlockSize * pos);
359 if (count < 32) {
360 return kBlocksPerSuperBlock * s_block + pos + count - 1;
361 }
362 }
363 return kBlocksPerSuperBlock * s_block + kBlocksPerSuperBlock - 1;
364 }
365
383 uint64_t find_basicblock_is(uint16_t local_rank, uint64_t s_block) const {
384 auto super_block_rank = super_block_rank_.AsConst64BitInts();
385 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
386
387 auto lower = super_block_rank[s_block];
388 auto upper = super_block_rank[s_block + 1];
389
390 uint64_t pos = kBlocksPerSuperBlock * local_rank / (upper - lower);
391 pos = pos + 16 < 32 ? 0 : (pos - 16);
392 pos = pos > 96 ? 96 : pos;
393 while (pos < 96) {
394 auto count = lower_bound_32x16(
395 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
396 if (count == 0) {
397 return find_basicblock(local_rank, s_block);
398 }
399 if (count < 32) {
400 return kBlocksPerSuperBlock * s_block + pos + count - 1;
401 }
402 pos += 32;
403 }
404 pos = 96;
405 auto count = lower_bound_32x16(
406 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank);
407 if (count == 0) {
408 return find_basicblock(local_rank, s_block);
409 }
410 return kBlocksPerSuperBlock * s_block + pos + count - 1;
411 }
412
431 uint64_t find_basicblock_is_zeros(uint16_t local_rank0,
432 uint64_t s_block) const {
433 auto super_block_rank = super_block_rank_.AsConst64BitInts();
434 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
435
436 auto lower = kSuperBlockSize * s_block - super_block_rank[s_block];
437 auto upper =
438 kSuperBlockSize * (s_block + 1) - super_block_rank[s_block + 1];
439
440 uint64_t pos = kBlocksPerSuperBlock * local_rank0 / (upper - lower);
441 pos = pos + 16 < 32 ? 0 : (pos - 16);
442 pos = pos > 96 ? 96 : pos;
443 while (pos < 96) {
444 auto count = lower_bound_delta_32x16(
445 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
446 delta_basic, kBasicBlockSize * pos);
447 if (count == 0) {
448 return find_basicblock_zeros(local_rank0, s_block);
449 }
450 if (count < 32) {
451 return kBlocksPerSuperBlock * s_block + pos + count - 1;
452 }
453 pos += 32;
454 }
455 pos = 96;
456 auto count = lower_bound_delta_32x16(
457 &basic_block_rank[kBlocksPerSuperBlock * s_block + pos], local_rank0,
458 delta_basic, kBasicBlockSize * pos);
459 if (count == 0) {
460 return find_basicblock_zeros(local_rank0, s_block);
461 }
462 return kBlocksPerSuperBlock * s_block + pos + count - 1;
463 }
464
465 public:
466 BitVector() = default;
467 BitVector(const BitVector&) = default;
468 BitVector(BitVector&&) noexcept = default;
469 BitVector& operator=(const BitVector&) = default;
470 BitVector& operator=(BitVector&&) noexcept = default;
471
472#ifdef PIXIE_DIAGNOSTICS
473 struct DiagnosticsBytes {
474 size_t source_bitvector_bytes = 0;
475 size_t super_block_rank_bytes = 0;
476 size_t basic_block_rank_bytes = 0;
477 size_t select1_samples_bytes = 0;
478 size_t select0_samples_bytes = 0;
479 size_t total_bytes = 0;
480 };
481
485 DiagnosticsBytes diagnostics_bytes() const {
486 DiagnosticsBytes result;
487 result.source_bitvector_bytes = (num_bits_ + 7) / 8;
488 result.super_block_rank_bytes = super_block_rank_.AsConstBytes().size();
489 result.basic_block_rank_bytes = basic_block_rank_.AsConstBytes().size();
490 result.select1_samples_bytes = select1_samples_.AsConstBytes().size();
491 result.select0_samples_bytes = select0_samples_.AsConstBytes().size();
492 result.total_bytes =
493 result.super_block_rank_bytes + result.basic_block_rank_bytes +
494 result.select1_samples_bytes + result.select0_samples_bytes;
495 return result;
496 }
497
501 void memory_report() const {
502 const auto diagnostics = diagnostics_bytes();
503 const double source_bytes =
504 static_cast<double>(diagnostics.source_bitvector_bytes);
505 const auto log_bytes = [&](std::string_view label, size_t bytes) {
506 const double percentage =
507 source_bytes > 0.0 ? 100.0 * static_cast<double>(bytes) / source_bytes
508 : 0.0;
509 spdlog::info("BitVector {}: {} bytes ({:.2f}% of source)", label, bytes,
510 percentage);
511 };
512 log_bytes("source_bitvector", diagnostics.source_bitvector_bytes);
513 log_bytes("super_block_rank", diagnostics.super_block_rank_bytes);
514 log_bytes("basic_block_rank", diagnostics.basic_block_rank_bytes);
515 log_bytes("select1_samples", diagnostics.select1_samples_bytes);
516 log_bytes("select0_samples", diagnostics.select0_samples_bytes);
517 log_bytes("total", diagnostics.total_bytes);
518 }
519#endif
527 explicit BitVector(std::span<const uint64_t> bit_vector, size_t num_bits)
528 : num_bits_(std::min(num_bits, bit_vector.size() * kWordSize)),
529 padded_size_(((num_bits_ + kWordSize - 1) / kWordSize) * kWordSize),
530 bits_(bit_vector) {
531 build_rank();
532 build_select();
533 }
534
538 size_t size() const { return num_bits_; }
539
545 int operator[](size_t pos) const {
546 size_t word_idx = pos / kWordSize;
547 size_t bit_off = pos % kWordSize;
548
549 return (bits_[word_idx] >> bit_off) & 1;
550 }
551
558 uint64_t rank(size_t pos) const {
559 if (pos >= num_bits_) [[unlikely]] {
560 return max_rank_;
561 }
562
563 auto super_block_rank = super_block_rank_.AsConst64BitInts();
564 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
565
566 uint64_t b_block = pos / kBasicBlockSize;
567 uint64_t s_block = pos / kSuperBlockSize;
568 // Precomputed rank
569 uint64_t result = super_block_rank[s_block] + basic_block_rank[b_block];
570 // Basic block tail
571 result += rank_in_basic_block(b_block, pos - (b_block * kBasicBlockSize));
572 return result;
573 }
574
581 uint64_t rank0(size_t pos) const {
582 if (pos >= num_bits_) [[unlikely]] {
583 return num_bits_ - max_rank_;
584 }
585 return pos - rank(pos);
586 }
587
595 uint64_t select(size_t rank) const {
596 if (rank > max_rank_) [[unlikely]] {
597 return num_bits_;
598 }
599 if (rank == 0) [[unlikely]] {
600 return 0;
601 }
602 auto super_block_rank = super_block_rank_.AsConst64BitInts();
603 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
604
605 uint64_t s_block = find_superblock(rank);
606 rank -= super_block_rank[s_block];
607 auto pos = find_basicblock_is(rank, s_block);
608 rank -= basic_block_rank[pos];
609 return select_in_words(pos * kWordsPerBlock, rank, true);
610 }
611
619 uint64_t select0(size_t rank0) const {
620 if (rank0 > num_bits_ - max_rank_) [[unlikely]] {
621 return num_bits_;
622 }
623 if (rank0 == 0) [[unlikely]] {
624 return 0;
625 }
626 auto super_block_rank = super_block_rank_.AsConst64BitInts();
627 auto basic_block_rank = basic_block_rank_.AsConst16BitInts();
628
629 uint64_t s_block = find_superblock_zeros(rank0);
630 rank0 -= kSuperBlockSize * s_block - super_block_rank[s_block];
631 auto pos = find_basicblock_is_zeros(rank0, s_block);
632 auto pos_in_super_block = pos & (kBlocksPerSuperBlock - 1);
633 rank0 -= kBasicBlockSize * pos_in_super_block - basic_block_rank[pos];
634 return select_in_words(pos * kWordsPerBlock, rank0, false);
635 }
636
640 std::string to_string() const {
641 std::string result;
642 result.reserve(num_bits_);
643
644 for (size_t i = 0; i < num_bits_; i++) {
645 result.push_back(operator[](i) ? '1' : '0');
646 }
647
648 return result;
649 }
650};
651
670 private:
671 constexpr static size_t kWordSize = 64;
672 constexpr static size_t kSuperBlockRankIntSize = 64;
673 constexpr static size_t kBasicBlockRankIntSize = 16;
677 constexpr static size_t kBasicBlockSize = 496;
684 constexpr static size_t kSuperBlockSize = 63488;
685 constexpr static size_t kBlocksPerSuperBlock = 128;
686 constexpr static size_t kWordsPerBlock = 8;
687
688 const size_t num_bits_;
689 std::vector<uint64_t> bits_interleaved;
690 std::vector<uint64_t> super_block_rank_;
691
692 class BitReader {
693 size_t iterator_64_ = 0;
694 size_t offset_size_ = 0;
695 size_t offset_bits_ = 0;
696 std::span<const uint64_t> bits_;
697
698 public:
699 BitReader(std::span<const uint64_t> bits) : bits_(bits) {}
700 uint64_t ReadBits64(size_t num_bits) {
701 if (num_bits > 64) {
702 num_bits = 64;
703 }
704 uint64_t result = offset_bits_ & first_bits_mask(num_bits);
705 if (offset_size_ >= num_bits) {
706 offset_bits_ >>= num_bits;
707 offset_size_ -= num_bits;
708 return result;
709 }
710 uint64_t next = iterator_64_ < bits_.size() ? bits_[iterator_64_++] : 0;
711 result ^= (next & first_bits_mask(num_bits - offset_size_))
712 << offset_size_;
713 offset_bits_ = (num_bits - offset_size_ == 64)
714 ? 0
715 : next >> (num_bits - offset_size_);
716 offset_size_ = 64 - (num_bits - offset_size_);
717 return result;
718 }
719 };
720
721 public:
729 explicit BitVectorInterleaved(std::span<const uint64_t> bit_vector,
730 size_t num_bits)
731 : num_bits_(std::min(num_bits, bit_vector.size() * kWordSize)) {
732 build_rank_interleaved(bit_vector, num_bits);
733 }
734
738 static inline uint64_t first_bits_mask(size_t num) {
739 return num >= 64 ? UINT64_MAX : ((1llu << num) - 1);
740 }
741
745 size_t size() const { return num_bits_; }
746
752 int operator[](size_t pos) const {
753 size_t block_id = pos / kBasicBlockSize;
754 size_t block_bit = pos - block_id * kBasicBlockSize;
755 size_t word_id = block_id * kWordsPerBlock + block_bit / kWordSize;
756 size_t word_bit = block_bit % kWordSize;
757 kWordSize;
758
759 return (bits_interleaved[word_id] >> word_bit) & 1;
760 }
761
769 void build_rank_interleaved(std::span<const uint64_t> bits, size_t num_bits) {
770 size_t num_superblocks = 1 + (num_bits_ - 1) / kSuperBlockSize;
771 super_block_rank_.resize(num_superblocks);
772 size_t num_basicblocks = 1 + (num_bits_ - 1) / kBasicBlockSize;
773 bits_interleaved.resize(num_basicblocks * (512 / kWordSize));
774
775 uint64_t super_block_sum = 0;
776 uint16_t basic_block_sum = 0;
777 auto bit_reader = BitReader(bits);
778
779 for (size_t i = 0; i * kBasicBlockSize < num_bits; ++i) {
780 if (i % (kSuperBlockSize / kBasicBlockSize) == 0) {
781 super_block_sum += basic_block_sum;
782 super_block_rank_[i / (kSuperBlockSize / kBasicBlockSize)] =
783 super_block_sum;
784 basic_block_sum = 0;
785 }
786 bits_interleaved[i * (kWordsPerBlock) + 7] =
787 static_cast<uint64_t>(basic_block_sum) << 48;
788
789 for (size_t j = 0; j < 7 && kWordSize * (i + j) < num_bits; ++j) {
790 bits_interleaved[i * (kWordsPerBlock) + j] =
791 bit_reader.ReadBits64(std::min<uint64_t>(
792 64ull, num_bits - i * kBasicBlockSize + j * kWordSize));
793 basic_block_sum +=
794 std::popcount(bits_interleaved[i * (kWordsPerBlock) + j]);
795 }
796 if ((i + 7) * kWordSize < num_bits) {
797 auto v = bit_reader.ReadBits64(std::min<uint64_t>(
798 48ull, num_bits - (i * kBasicBlockSize + 7 * kWordSize)));
799 bits_interleaved[i * (kWordsPerBlock) + 7] ^= v;
800 basic_block_sum += std::popcount(v);
801 }
802 }
803 }
804
811 uint64_t rank(size_t pos) const {
812 // Multiplication/devisions
813 uint64_t b_block = pos / kBasicBlockSize;
814 uint64_t s_block = b_block / kBlocksPerSuperBlock;
815 uint64_t b_block_pos = b_block * kWordsPerBlock;
816 // Super block rank
817 uint64_t result = super_block_rank_[s_block];
824 // __builtin_prefetch(&bits_interleaved[b_block_pos]);
825 result += rank_512(&bits_interleaved[b_block_pos],
826 pos - (b_block * kBasicBlockSize));
827 result += bits_interleaved[b_block_pos + 7] >> 48;
828 return result;
829 }
830
834 std::string to_string() const {
835 std::string result;
836 result.reserve(num_bits_);
837
838 for (size_t i = 0; i < num_bits_; i++) {
839 result.push_back(operator[](i) ? '1' : '0');
840 }
841
842 return result;
843 }
844};
845
846} // namespace pixie
A simple aligned storage for cache-line sized blocks.
Definition cache_line.h:23
int operator[](size_t pos) const
Returns the bit at the given position.
Definition bitvector.h:752
size_t size() const
Returns the number of valid bits.
Definition bitvector.h:745
std::string to_string() const
Convert to a binary string (debug helper).
Definition bitvector.h:834
BitVectorInterleaved(std::span< const uint64_t > bit_vector, size_t num_bits)
Construct from an external array of 64-bit words.
Definition bitvector.h:729
uint64_t rank(size_t pos) const
Rank of 1s up to position pos (exclusive).
Definition bitvector.h:811
static uint64_t first_bits_mask(size_t num)
Mask with the lowest num bits set.
Definition bitvector.h:738
void build_rank_interleaved(std::span< const uint64_t > bits, size_t num_bits)
Build the interleaved layout and rank index.
Definition bitvector.h:769
BitVector(std::span< const uint64_t > bit_vector, size_t num_bits)
Construct from an external array of 64-bit words.
Definition bitvector.h:527
size_t size() const
Returns the number of valid bits.
Definition bitvector.h:538
uint64_t rank(size_t pos) const
Rank of 1s up to position pos (exclusive).
Definition bitvector.h:558
int operator[](size_t pos) const
Returns the bit at the given position.
Definition bitvector.h:545
uint64_t select0(size_t rank0) const
Select the position of the rank0-th 0-bit (1-indexed).
Definition bitvector.h:619
uint64_t select(size_t rank) const
Select the position of the rank-th 1-bit (1-indexed).
Definition bitvector.h:595
std::string to_string() const
Convert to a binary string (debug helper).
Definition bitvector.h:640
uint64_t rank0(size_t pos) const
Rank of 0s up to position pos (exclusive).
Definition bitvector.h:581