sparrow 0.9.0
Loading...
Searching...
No Matches
dynamic_bitset_base.hpp
Go to the documentation of this file.
1// Copyright 2024 Man Group Operations Limited
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17
18#include <algorithm>
19#include <bit>
20#include <stdexcept>
21#include <string>
22#include <type_traits>
23
27
28namespace sparrow
29{
41 template <typename B>
42 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
44 {
45 public:
48 using storage_type = B;
49 using storage_type_without_cvrefpointer = std::remove_pointer_t<std::remove_cvref_t<storage_type>>;
50 using block_type = typename storage_type_without_cvrefpointer::value_type;
51 using value_type = bool;
53 using const_reference = bool;
54 using size_type = typename storage_type_without_cvrefpointer::size_type;
55 using difference_type = typename storage_type_without_cvrefpointer::difference_type;
58
59 [[nodiscard]] constexpr size_type size() const noexcept;
60 [[nodiscard]] constexpr bool empty() const noexcept;
61 [[nodiscard]] constexpr size_type null_count() const noexcept;
62
63 [[nodiscard]] constexpr bool test(size_type pos) const;
64 constexpr void set(size_type pos, value_type value);
65
66 [[nodiscard]] constexpr const_reference at(size_type pos) const;
67 [[nodiscard]] constexpr reference at(size_type pos);
68
69 [[nodiscard]] constexpr reference operator[](size_type i);
70 [[nodiscard]] constexpr const_reference operator[](size_type i) const;
71
72 [[nodiscard]] constexpr block_type* data() noexcept;
73 [[nodiscard]] constexpr const block_type* data() const noexcept;
74 [[nodiscard]] constexpr size_type block_count() const noexcept;
76 constexpr void swap(self_type&) noexcept;
78 [[nodiscard]] constexpr iterator begin();
79 [[nodiscard]] constexpr iterator end();
80
81 [[nodiscard]] constexpr const_iterator begin() const;
82 [[nodiscard]] constexpr const_iterator end() const;
83 [[nodiscard]] constexpr const_iterator cbegin() const;
84 [[nodiscard]] constexpr const_iterator cend() const;
85
86 [[nodiscard]] constexpr reference front();
87 [[nodiscard]] constexpr const_reference front() const;
88 [[nodiscard]] constexpr reference back();
89 [[nodiscard]] constexpr const_reference back() const;
90
91 [[nodiscard]] constexpr const storage_type_without_cvrefpointer& buffer() const noexcept
92 {
93 if constexpr (std::is_pointer_v<storage_type>)
94 {
95 return *m_buffer;
96 }
97 else
98 {
99 return m_buffer;
100 }
101 }
102
103 [[nodiscard]] constexpr storage_type_without_cvrefpointer& buffer() noexcept
104 {
105 if constexpr (std::is_pointer_v<storage_type>)
106 {
107 return *m_buffer;
108 }
109 else
110 {
111 return m_buffer;
112 }
113 }
114
115 [[nodiscard]] static constexpr size_type compute_block_count(size_type bits_count) noexcept;
116
117 // storage_type is a value_type
118 [[nodiscard]] storage_type extract_storage() noexcept
120 {
121 return std::move(m_buffer);
122 }
123
124 protected:
125
128 constexpr ~dynamic_bitset_base() = default;
129
130 constexpr dynamic_bitset_base(const dynamic_bitset_base&) = default;
131 constexpr dynamic_bitset_base(dynamic_bitset_base&&) noexcept = default;
132
133 constexpr dynamic_bitset_base& operator=(const dynamic_bitset_base&) = default;
134 constexpr dynamic_bitset_base& operator=(dynamic_bitset_base&&) noexcept = default;
135
136 constexpr void resize(size_type n, value_type b = false);
137 constexpr void clear() noexcept;
138
140 constexpr iterator insert(const_iterator pos, size_type count, value_type value);
141 template <std::input_iterator InputIt>
142 constexpr iterator insert(const_iterator pos, InputIt first, InputIt last);
143 constexpr iterator insert(const_iterator pos, std::initializer_list<value_type> ilist);
147
148 constexpr void push_back(value_type value);
149 constexpr void pop_back();
150
151 constexpr void zero_unused_bits();
152 [[nodiscard]] size_type count_non_null() const noexcept;
153
154 private:
155
156 static constexpr std::size_t s_bits_per_block = sizeof(block_type) * CHAR_BIT;
157 [[nodiscard]] static constexpr size_type block_index(size_type pos) noexcept;
158 [[nodiscard]] static constexpr size_type bit_index(size_type pos) noexcept;
159 [[nodiscard]] static constexpr block_type bit_mask(size_type pos) noexcept;
160
161 [[nodiscard]] constexpr size_type count_extra_bits() const noexcept;
162 constexpr void update_null_count(bool old_value, bool new_value);
163
164 storage_type m_buffer;
165 size_type m_size;
166 size_type m_null_count;
167
168 friend class bitset_iterator<self_type, true>;
169 friend class bitset_iterator<self_type, false>;
170 friend class bitset_reference<self_type>;
171 };
172
173 template <typename B>
174 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
175 constexpr auto dynamic_bitset_base<B>::size() const noexcept -> size_type
176 {
177 return m_size;
178 }
179
180 template <typename B>
181 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
182 constexpr bool dynamic_bitset_base<B>::empty() const noexcept
183 {
184 return m_size == 0;
185 }
186
187 template <typename B>
188 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
189 constexpr auto dynamic_bitset_base<B>::null_count() const noexcept -> size_type
190 {
191 return m_null_count;
192 }
193
194 template <typename B>
195 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
197 {
198 SPARROW_ASSERT_TRUE(pos < size());
199 return reference(*this, pos);
200 }
201
202 template <typename B>
203 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
205 {
206 return test(pos);
207 }
208
209 template <typename B>
210 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
211 constexpr bool dynamic_bitset_base<B>::test(size_type pos) const
212 {
213 SPARROW_ASSERT_TRUE(pos < size());
214 if constexpr (std::is_pointer_v<storage_type>)
215 {
216 if (m_buffer == nullptr)
217 {
218 return true;
219 }
220 }
221 if (data() == nullptr)
222 {
223 return true;
224 }
225 return !m_null_count || buffer().data()[block_index(pos)] & bit_mask(pos);
226 }
227
228 template <typename B>
229 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
231 {
232 SPARROW_ASSERT_TRUE(pos < size());
233 if (data() == nullptr)
234 {
235 if (value == true) // In this case, we don't need to set the bit
236 {
237 return;
238 }
239 else
240 {
241 if constexpr (requires(storage_type_without_cvrefpointer s) { s.resize(0); })
242 {
243 constexpr block_type true_value = block_type(~block_type(0));
244 const auto block_count = compute_block_count(size());
245 buffer().resize(block_count, true_value);
247 }
248 else
249 {
250 throw std::runtime_error("Cannot set a bit in a null buffer.");
251 }
252 }
253 }
254 block_type& block = buffer().data()[block_index(pos)];
255 const bool old_value = block & bit_mask(pos);
256 if (value)
257 {
258 block |= bit_mask(pos);
259 }
260 else
261 {
262 block &= block_type(~bit_mask(pos));
263 }
264 update_null_count(old_value, value);
265 }
266
267 template <typename B>
268 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
269 constexpr auto dynamic_bitset_base<B>::data() noexcept -> block_type*
270 {
271 if constexpr (std::is_pointer_v<storage_type>)
272 {
273 if (m_buffer == nullptr)
274 {
275 return nullptr;
276 }
277 }
278 return buffer().data();
279 }
280
281 template <typename B>
282 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
283 constexpr auto dynamic_bitset_base<B>::data() const noexcept -> const block_type*
284 {
285 return buffer().data();
286 }
287
288 template <typename B>
289 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
290 constexpr auto dynamic_bitset_base<B>::block_count() const noexcept -> size_type
291 {
292 return buffer().size();
293 }
294
295 template <typename B>
296 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
297 constexpr void dynamic_bitset_base<B>::swap(self_type& rhs) noexcept
298 {
299 using std::swap;
300 swap(m_buffer, rhs.m_buffer);
301 swap(m_size, rhs.m_size);
302 swap(m_null_count, rhs.m_null_count);
303 }
304
305 template <typename B>
306 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
308 {
309 return iterator(this, 0u);
310 }
311
312 template <typename B>
313 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
315 {
316 return iterator(this, size());
317 }
318
319 template <typename B>
320 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
322 {
323 return cbegin();
324 }
325
326 template <typename B>
327 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
329 {
330 return cend();
331 }
332
333 template <typename B>
334 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
336 {
337 return const_iterator(this, 0);
338 }
339
340 template <typename B>
341 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
343 {
344 return const_iterator(this, size());
345 }
346
347 template <typename B>
348 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
350 {
351 if (pos >= size())
352 {
353 throw std::out_of_range(
354 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
355 + std::to_string(size()) + " at index " + std::to_string(pos)
356 );
357 }
358 return (*this)[pos];
359 }
360
361 template <typename B>
362 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
364 {
365 if (pos >= size())
366 {
367 throw std::out_of_range(
368 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
369 + std::to_string(size()) + " at index " + std::to_string(pos)
370 );
371 }
372 return test(pos);
373 }
374
375 template <typename B>
376 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
378 {
380 return (*this)[0];
381 }
382
383 template <typename B>
384 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
386 {
388 if (data() == nullptr)
389 {
390 return true;
391 }
392 return (*this)[0];
393 }
394
395 template <typename B>
396 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
398 {
400 return (*this)[size() - 1];
401 }
402
403 template <typename B>
404 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
406 {
408 if (data() == nullptr)
409 {
410 return true;
411 }
412 return (*this)[size() - 1];
413 }
414
415 template <typename B>
416 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
418 : m_buffer(std::move(buf))
419 , m_size(size)
420 , m_null_count(m_size - count_non_null())
421 {
422 }
423
424 template <typename B>
425 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
427 : m_buffer(std::move(buf))
428 , m_size(size)
429 , m_null_count(null_count)
430 {
431 }
432
433 template <typename B>
434 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
436 {
437 return bits_count / s_bits_per_block + static_cast<size_type>(bits_count % s_bits_per_block != 0);
438 }
439
440 template <typename B>
441 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
442 constexpr auto dynamic_bitset_base<B>::block_index(size_type pos) noexcept -> size_type
443 {
444 return pos / s_bits_per_block;
445 }
446
447 template <typename B>
448 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
449 constexpr auto dynamic_bitset_base<B>::bit_index(size_type pos) noexcept -> size_type
450 {
451 return pos % s_bits_per_block;
452 }
453
454 template <typename B>
455 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
456 constexpr auto dynamic_bitset_base<B>::bit_mask(size_type pos) noexcept -> block_type
457 {
458 const size_type bit = bit_index(pos);
459 return static_cast<block_type>(block_type(1) << bit);
460 }
461
462 template <typename B>
463 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
465 {
466 if constexpr (std::is_pointer_v<storage_type>)
467 {
468 if (m_buffer == nullptr)
469 {
470 return m_size;
471 }
472 }
473 if (data() == nullptr || buffer().empty())
474 {
475 return m_size;
476 }
477
478 int res = 0;
479 size_t full_blocks = m_size / s_bits_per_block;
480 for (size_t i = 0; i < full_blocks; ++i)
481 {
482 res += std::popcount(buffer().data()[i]);
483 }
484 if (full_blocks != buffer().size())
485 {
486 const size_t bits_count = m_size % s_bits_per_block;
487 const block_type mask = ~block_type(~block_type(0) << bits_count);
488 const block_type block = buffer().data()[full_blocks] & mask;
489 res += std::popcount(block);
490 }
491
492 return static_cast<size_t>(res);
493 }
494
495 template <typename B>
496 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
497 constexpr auto dynamic_bitset_base<B>::count_extra_bits() const noexcept -> size_type
498 {
499 return bit_index(size());
500 }
501
502 template <typename B>
503 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
505 {
506 if (data() == nullptr)
507 {
508 return;
509 }
510 const size_type extra_bits = count_extra_bits();
511 if (extra_bits != 0)
512 {
513 buffer().back() &= block_type(~(~block_type(0) << extra_bits));
514 }
515 }
516
517 template <typename B>
518 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
519 constexpr void dynamic_bitset_base<B>::update_null_count(bool old_value, bool new_value)
520 {
521 if (new_value && !old_value)
522 {
523 --m_null_count;
524 }
525 else if (!new_value && old_value)
526 {
527 ++m_null_count;
528 }
529 }
530
531 template <typename B>
532 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
534 {
535 if ((data() == nullptr) && b)
536 {
537 m_size = n;
538 return;
539 }
540 size_type old_block_count = buffer().size();
541 const size_type new_block_count = compute_block_count(n);
542 const block_type value = b ? block_type(~block_type(0)) : block_type(0);
543
544 if (new_block_count != old_block_count)
545 {
546 if (data() == nullptr)
547 {
548 constexpr block_type true_value = block_type(~block_type(0));
549 old_block_count = compute_block_count(size());
550 buffer().resize(old_block_count, true_value);
552 }
553 buffer().resize(new_block_count, value);
554 }
555
556 if (b && (n > m_size))
557 {
558 const size_type extra_bits = count_extra_bits();
559 if (extra_bits > 0)
560 {
561 buffer().data()[old_block_count - 1] |= static_cast<block_type>(value << extra_bits);
562 }
563 }
564
565 m_size = n;
566 m_null_count = m_size - count_non_null();
568 }
569
570 template <typename B>
571 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
572 constexpr void dynamic_bitset_base<B>::clear() noexcept
573 {
574 buffer().clear();
575 m_size = 0;
576 m_null_count = 0;
577 }
578
579 template <typename B>
580 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
583 {
584 return insert(pos, 1, value);
585 }
586
587 template <typename B>
588 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
591 {
592 SPARROW_ASSERT_TRUE(cbegin() <= pos);
593 SPARROW_ASSERT_TRUE(pos <= cend());
594 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
595 if (data() == nullptr && value)
596 {
597 m_size += count;
598 }
599 else
600 {
601 const size_type old_size = size();
602 const size_type new_size = old_size + count;
603
604 // TODO: The current implementation is not efficient. It can be improved.
605
606 resize(new_size);
607
608 for (size_type i = old_size + count - 1; i >= index + count; --i)
609 {
610 set(i, test(i - count));
611 }
612
613 for (size_type i = 0; i < count; ++i)
614 {
615 set(index + i, value);
616 }
617 }
618
619 return iterator(this, index);
620 }
621
622 template <typename B>
623 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
624 template <std::input_iterator InputIt>
626 dynamic_bitset_base<B>::insert(const_iterator pos, InputIt first, InputIt last)
627 {
628 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
629 const auto count = static_cast<size_type>(std::distance(first, last));
630 if (data() == nullptr)
631 {
632 if (std::all_of(
633 first,
634 last,
635 [](auto v)
636 {
637 return bool(v);
638 }
639 ))
640 {
641 m_size += count;
642 }
643 return iterator(this, index);
644 }
645 SPARROW_ASSERT_TRUE(cbegin() <= pos);
646 SPARROW_ASSERT_TRUE(pos <= cend());
647
648 const size_type old_size = size();
649 const size_type new_size = old_size + count;
650
651 resize(new_size);
652
653 // TODO: The current implementation is not efficient. It can be improved.
654
655 for (size_type i = old_size + count - 1; i >= index + count; --i)
656 {
657 set(i, test(i - count));
658 }
659
660 for (size_type i = 0; i < count; ++i)
661 {
662 set(index + i, *first++);
663 }
664
665 return iterator(this, index);
666 }
667
668 template <typename B>
669 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
672 {
673 return insert(pos, value);
674 }
675
676 template <typename B>
677 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
679 {
680 SPARROW_ASSERT_TRUE(cbegin() <= pos);
681 SPARROW_ASSERT_TRUE(pos < cend());
682
683 return erase(pos, pos + 1);
684 }
685
686 template <typename B>
687 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
690 {
691 SPARROW_ASSERT_TRUE(cbegin() <= first);
692 SPARROW_ASSERT_TRUE(first <= last);
693 SPARROW_ASSERT_TRUE(last <= cend());
694
695 const auto first_index = static_cast<size_type>(std::distance(cbegin(), first));
696 const auto last_index = static_cast<size_type>(std::distance(cbegin(), last));
697 const size_type count = last_index - first_index;
698
699 if (data() == nullptr)
700 {
701 m_size -= count;
702 }
703 else
704 {
705 if (last == cend())
706 {
707 resize(first_index);
708 return end();
709 }
710
711 // TODO: The current implementation is not efficient. It can be improved.
712
713 const size_type bit_to_move = size() - last_index;
714 for (size_type i = 0; i < bit_to_move; ++i)
715 {
716 set(first_index + i, test(last_index + i));
717 }
718
719 resize(size() - count);
720 }
721 return iterator(this, first_index);
722 }
723
724 template <typename B>
725 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
727 {
728 resize(size() + 1, value);
729 }
730
731 template <typename B>
732 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
734 {
735 if (empty())
736 {
737 return;
738 }
739 resize(size() - 1);
740 }
741}
Iterator used to iterate over the bits of a dynamic bitset as if they were addressable values.
Reference proxy used by the bitset_iterator class to make it possible to assign a bit of a bitset as ...
typename storage_type_without_cvrefpointer::value_type block_type
constexpr ~dynamic_bitset_base()=default
bitset_iterator< self_type, false > iterator
constexpr iterator insert(const_iterator pos, value_type value)
constexpr const_iterator cbegin() const
size_type count_non_null() const noexcept
constexpr void push_back(value_type value)
constexpr iterator erase(const_iterator pos)
typename storage_type_without_cvrefpointer::size_type size_type
constexpr bool empty() const noexcept
constexpr iterator emplace(const_iterator pos, value_type value)
dynamic_bitset_base< buffer< T > * > self_type
storage_type extract_storage() noexcept
constexpr const_reference at(size_type pos) const
constexpr bool test(size_type pos) const
constexpr void resize(size_type n, value_type b=false)
constexpr size_type null_count() const noexcept
bitset_iterator< self_type, true > const_iterator
std::remove_pointer_t< std::remove_cvref_t< storage_type > > storage_type_without_cvrefpointer
static constexpr size_type compute_block_count(size_type bits_count) noexcept
constexpr size_type size() const noexcept
constexpr dynamic_bitset_base(dynamic_bitset_base &&) noexcept=default
constexpr void set(size_type pos, value_type value)
typename storage_type_without_cvrefpointer::difference_type difference_type
constexpr size_type block_count() const noexcept
constexpr const storage_type_without_cvrefpointer & buffer() const noexcept
constexpr reference operator[](size_type i)
constexpr dynamic_bitset_base(storage_type buffer, size_type size, size_type null_count)
constexpr dynamic_bitset_base(const dynamic_bitset_base &)=default
constexpr void swap(self_type &) noexcept
constexpr block_type * data() noexcept
constexpr dynamic_bitset_base(storage_type buffer, size_type size)
constexpr const_iterator cend() const
constexpr storage_type_without_cvrefpointer & buffer() noexcept
typename base_type::block_type block_type
#define SPARROW_ASSERT_TRUE(expr__)