FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
bitset.h
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <cstddef>
5#include <cstdint>
6
7#include <algorithm>
8#include <bit>
9#include <initializer_list>
10#include <iostream>
11#include <iterator>
12#include <ostream>
13#include <ranges>
14#include <utility>
15
16#include "fe/algo.h"
17#include "fe/hash.h"
18
19namespace fe {
20
21/// A dynamically growing set of bits with small storage optimization.
22/// The first Bitset::Inline_Bits bits live inside the Bitset itself; only beyond that it allocates on the heap.
23/// Think of a Bitset as an infinite sequence of bits that are zero except for the ones you set:
24/// Bitset::set/Bitset::flip grow the storage on demand,
25/// while testing or clearing a bit beyond Bitset::capacity needs no storage at all.
26/// @note Everything is `constexpr` as long as the Bitset stays inline:
27/// the heap words hide in a `uint64_t` and are only recovered by a `std::bit_cast` that a constant evaluation rejects.
28class Bitset {
29public:
30 static constexpr size_t Bits_Per_Word = sizeof(uint64_t) * 8; ///< Number of bits in one word.
31 static constexpr size_t Inline_Bits = Bits_Per_Word; ///< Number of bits available without allocating.
32 static constexpr size_t npos = size_t(-1); ///< Returned by Bitset::next if there is no set bit.
33
34 /// Proxy that Bitset::operator[] hands out to read/write the bit it refers to.
35 class reference {
36 public:
37 constexpr reference& operator=(bool b) noexcept {
38 bitset_->set(i_, b);
39 return *this;
40 }
41 constexpr reference& operator=(const reference& other) noexcept { return *this = bool(other); }
42 constexpr reference& flip() noexcept {
43 bitset_->flip(i_);
44 return *this;
45 }
46
47 constexpr operator bool() const noexcept { return bitset_->test(i_); }
48 constexpr bool operator~() const noexcept { return !bitset_->test(i_); }
49
50 private:
51 constexpr reference(Bitset* bitset, size_t i) noexcept
52 : bitset_(bitset)
53 , i_(i) {}
54
55 Bitset* bitset_;
56 size_t i_;
57
58 friend class Bitset;
59 };
60
61 /// Iterates over the *indices* of all set bits in ascending order.
62 class iterator {
63 public:
64 using iterator_category = std::forward_iterator_tag;
65 using value_type = size_t;
66 using difference_type = std::ptrdiff_t;
67 using pointer = void;
68 using reference = size_t;
69
70 constexpr iterator() noexcept = default;
71
72 constexpr size_t operator*() const noexcept { return i_; }
73 constexpr iterator& operator++() noexcept {
74 assert(i_ != npos);
75 i_ = bitset_->next(i_ + 1);
76 return *this;
77 }
78 constexpr iterator operator++(int) noexcept {
79 auto res = *this;
80 ++*this;
81 return res;
82 }
83 constexpr bool operator==(iterator other) const noexcept { return i_ == other.i_; }
84
85 private:
86 constexpr iterator(const Bitset* bitset, size_t i) noexcept
87 : bitset_(bitset)
88 , i_(i) {}
89
90 const Bitset* bitset_ = nullptr;
91 size_t i_ = npos;
92
93 friend class Bitset;
94 };
95
96 /// @name Constructors, Destructor, Assignment
97 ///@{
98 constexpr Bitset() noexcept = default;
99 constexpr Bitset(std::initializer_list<size_t> bits) {
100 for (auto i : bits)
101 set(i);
102 }
103 constexpr Bitset(const Bitset& other)
104 : num_words_(other.num_words_) {
105 if (other.on_heap()) {
106 auto heap = new uint64_t[num_words_];
107 std::copy_n(other.words(), num_words_, heap);
108 data_ = bitcast_resize<uint64_t>(heap);
109 } else {
110 data_ = other.data_;
111 }
112 }
113 constexpr Bitset(Bitset&& other) noexcept { swap(*this, other); }
114 constexpr ~Bitset() noexcept {
115 if (on_heap()) delete[] words();
116 }
117 constexpr Bitset& operator=(Bitset other) noexcept {
118 swap(*this, other);
119 return *this;
120 }
121 friend constexpr void swap(Bitset& b1, Bitset& b2) noexcept {
122 using std::swap;
123 swap(b1.data_, b2.data_);
124 swap(b1.num_words_, b2.num_words_);
125 }
126 ///@}
127
128 /// @name Access
129 ///@{
130 [[nodiscard]] constexpr bool test(size_t i) const noexcept {
131 auto w = i / Bits_Per_Word;
132 return w < num_words_ && (words()[w] & bit(i)) != 0;
133 }
134 [[nodiscard]] constexpr bool operator[](size_t i) const noexcept { return test(i); }
135 [[nodiscard]] constexpr reference operator[](size_t i) noexcept { return {this, i}; }
136
137 /// Index of the first bit that is set at or after @p i - or npos, if there is none.
138 [[nodiscard]] constexpr size_t next(size_t i) const noexcept {
139 auto w = i / Bits_Per_Word;
140 if (w >= num_words_) return npos;
141
142 auto words = this->words();
143 for (auto word = words[w] & (~uint64_t(0) << (i % Bits_Per_Word));; word = words[w]) {
144 if (word) return w * Bits_Per_Word + size_t(std::countr_zero(word));
145 if (++w == num_words_) return npos;
146 }
147 }
148 ///@}
149
150 /// @name Modifiers
151 /// Bitset::set and Bitset::flip grow the storage as needed; the others never allocate.
152 ///@{
153 constexpr Bitset& set(size_t i) {
154 grow(i / Bits_Per_Word + 1);
155 words()[i / Bits_Per_Word] |= bit(i);
156 return *this;
157 }
158 constexpr Bitset& set(size_t i, bool b) { return b ? set(i) : clear(i); }
159 constexpr Bitset& flip(size_t i) {
160 grow(i / Bits_Per_Word + 1);
161 words()[i / Bits_Per_Word] ^= bit(i);
162 return *this;
163 }
164 constexpr Bitset& clear(size_t i) noexcept {
165 if (auto w = i / Bits_Per_Word; w < num_words_) words()[w] &= ~bit(i);
166 return *this;
167 }
168 /// Clears all bits and releases the heap storage again.
169 constexpr Bitset& clear() noexcept {
170 if (on_heap()) delete[] words();
171 data_ = 0;
172 num_words_ = 1;
173 return *this;
174 }
175 ///@}
176
177 /// @name Queries
178 ///@{
179 [[nodiscard]] constexpr size_t count() const noexcept {
180 size_t res = 0;
181 for (size_t i = 0, e = num_words_; i != e; ++i)
182 res += size_t(std::popcount(words()[i]));
183 return res;
184 }
185 [[nodiscard]] constexpr bool any() const noexcept { return !zeros(words(), num_words_); }
186 [[nodiscard]] constexpr bool none() const noexcept { return zeros(words(), num_words_); }
187 /// Number of bits available without growing.
188 [[nodiscard]] constexpr size_t capacity() const noexcept { return num_words_ * Bits_Per_Word; }
189 /// Outgrown the inline storage?
190 [[nodiscard]] constexpr bool on_heap() const noexcept { return num_words_ != 1; }
191 ///@}
192
193 /// @name Set Operations
194 /// Union, intersection, symmetric difference, and difference.
195 ///@{
196 constexpr Bitset& operator|=(const Bitset& other) {
197 auto e = other.used();
198 grow(e);
199 for (size_t i = 0; i != e; ++i)
200 words()[i] |= other.words()[i];
201 return *this;
202 }
203 constexpr Bitset& operator&=(const Bitset& other) noexcept {
204 auto e = std::min(num_words_, other.num_words_);
205 for (size_t i = 0; i != e; ++i)
206 words()[i] &= other.words()[i];
207 for (size_t i = e, n = num_words_; i != n; ++i)
208 words()[i] = 0;
209 return *this;
210 }
211 constexpr Bitset& operator^=(const Bitset& other) {
212 auto e = other.used();
213 grow(e);
214 for (size_t i = 0; i != e; ++i)
215 words()[i] ^= other.words()[i];
216 return *this;
217 }
218 constexpr Bitset& operator-=(const Bitset& other) noexcept {
219 for (size_t i = 0, e = std::min(num_words_, other.num_words_); i != e; ++i)
220 words()[i] &= ~other.words()[i];
221 return *this;
222 }
223
224 friend constexpr Bitset operator|(Bitset b1, const Bitset& b2) { return std::move(b1 |= b2); }
225 friend constexpr Bitset operator&(Bitset b1, const Bitset& b2) { return std::move(b1 &= b2); }
226 friend constexpr Bitset operator^(Bitset b1, const Bitset& b2) { return std::move(b1 ^= b2); }
227 friend constexpr Bitset operator-(Bitset b1, const Bitset& b2) { return std::move(b1 -= b2); }
228 ///@}
229
230 /// @name Comparisons
231 /// The bits beyond Bitset::capacity are all zero, so a different capacity does *not* make two Bitset%s differ.
232 ///@{
233 [[nodiscard]] constexpr bool operator==(const Bitset& other) const noexcept {
234 auto e = std::min(num_words_, other.num_words_);
235 for (size_t i = 0; i != e; ++i)
236 if (words()[i] != other.words()[i]) return false;
237 return zeros(words() + e, num_words_ - e) && zeros(other.words() + e, other.num_words_ - e);
238 }
239
240 /// Is every bit set in `this` also set in @p other?
241 [[nodiscard]] constexpr bool subset_of(const Bitset& other) const noexcept {
242 auto e = std::min(num_words_, other.num_words_);
243 for (size_t i = 0; i != e; ++i)
244 if (words()[i] & ~other.words()[i]) return false;
245 return zeros(words() + e, num_words_ - e);
246 }
247
248 /// Do `this` and @p other have at least one bit in common?
249 [[nodiscard]] constexpr bool intersects(const Bitset& other) const noexcept {
250 for (size_t i = 0, e = std::min(num_words_, other.num_words_); i != e; ++i)
251 if (words()[i] & other.words()[i]) return true;
252 return false;
253 }
254 ///@}
255
256 /// @name Iterators
257 /// Yield the index of each set bit in ascending order.
258 ///@{
259 [[nodiscard]] constexpr iterator begin() const noexcept { return {this, next(0)}; }
260 [[nodiscard]] constexpr iterator end() const noexcept { return {}; }
261 ///@}
262
263 /// @name Hash
264 ///@{
265 [[nodiscard]] constexpr size_t hash() const noexcept {
266 auto res = hash_begin();
267 for (size_t i = 0, e = used(); i != e; ++i) {
268 auto word = words()[i];
269 res = hash_combine(res, uint32_t(word));
270 res = hash_combine(res, uint32_t(word >> 32));
271 }
272 return res;
273 }
274
275 struct Hash {
276 constexpr size_t operator()(const Bitset& bitset) const noexcept { return bitset.hash(); }
277 };
278
279#ifdef FE_ABSL
280 template<class H>
281 friend H AbslHashValue(H h, const Bitset& bitset) {
282 return H::combine(std::move(h), bitset.hash());
283 }
284#endif
285 ///@}
286
287 /// @name Output
288 ///@{
289 void dump() const { std::cout << (*this) << std::endl; }
290
291 friend std::ostream& operator<<(std::ostream& os, const Bitset& bitset) {
292 os << '{';
293 for (auto sep = ""; auto i : bitset) {
294 os << sep << i;
295 sep = ", ";
296 }
297 return os << '}';
298 }
299 ///@}
300
301private:
302 static constexpr uint64_t bit(size_t i) noexcept { return uint64_t(1) << (i % Bits_Per_Word); }
303
304 static constexpr bool zeros(const uint64_t* words, size_t num_words) noexcept {
305 for (size_t i = 0; i != num_words; ++i)
306 if (words[i]) return false;
307 return true;
308 }
309
310 constexpr uint64_t* words() noexcept { return on_heap() ? bitcast_resize<uint64_t*>(data_) : &data_; }
311 constexpr const uint64_t* words() const noexcept {
312 return on_heap() ? bitcast_resize<const uint64_t*>(data_) : &data_;
313 }
314
315 /// Number of words up to and including the last one that has a bit set.
316 constexpr size_t used() const noexcept {
317 for (auto i = num_words_; i-- != 0;)
318 if (words()[i]) return i + 1;
319 return 0;
320 }
321
322 constexpr void grow(size_t num_words) {
323 if (num_words <= num_words_) return;
324
325 num_words = std::max(num_words, num_words_ * 2);
326 auto heap = new uint64_t[num_words]();
327 std::copy_n(words(), num_words_, heap);
328 if (on_heap()) delete[] words();
329 data_ = bitcast_resize<uint64_t>(heap);
330 num_words_ = num_words;
331 }
332
333 uint64_t data_ = 0; ///< The bits themselves while inline, the heap words otherwise.
334 size_t num_words_ = 1; ///< Capacity in words; `1` means inline.
335};
336
337static_assert(sizeof(void*) != 8 || sizeof(Bitset) == 16, "Bitset should stay two machine words");
338static_assert(std::forward_iterator<Bitset::iterator>);
339static_assert(std::ranges::forward_range<Bitset>);
340
341} // namespace fe
342
343#ifndef DOXYGEN
344template<>
345struct std::hash<fe::Bitset> {
346 constexpr size_t operator()(const fe::Bitset& bitset) const noexcept { return bitset.hash(); }
347};
348#endif
Iterates over the indices of all set bits in ascending order.
Definition bitset.h:62
constexpr iterator & operator++() noexcept
Definition bitset.h:73
constexpr iterator() noexcept=default
std::forward_iterator_tag iterator_category
Definition bitset.h:64
std::ptrdiff_t difference_type
Definition bitset.h:66
friend class Bitset
Definition bitset.h:93
constexpr iterator operator++(int) noexcept
Definition bitset.h:78
constexpr bool operator==(iterator other) const noexcept
Definition bitset.h:83
Proxy that Bitset::operator[] hands out to read/write the bit it refers to.
Definition bitset.h:35
constexpr bool operator~() const noexcept
Definition bitset.h:48
constexpr reference & operator=(bool b) noexcept
Definition bitset.h:37
friend class Bitset
Definition bitset.h:58
constexpr reference & flip() noexcept
Definition bitset.h:42
constexpr reference & operator=(const reference &other) noexcept
Definition bitset.h:41
A dynamically growing set of bits with small storage optimization.
Definition bitset.h:28
constexpr Bitset(const Bitset &other)
Definition bitset.h:103
constexpr size_t capacity() const noexcept
Number of bits available without growing.
Definition bitset.h:188
constexpr bool none() const noexcept
Definition bitset.h:186
constexpr ~Bitset() noexcept
Definition bitset.h:114
constexpr size_t next(size_t i) const noexcept
Index of the first bit that is set at or after i - or npos, if there is none.
Definition bitset.h:138
constexpr Bitset & operator^=(const Bitset &other)
Definition bitset.h:211
constexpr Bitset & clear(size_t i) noexcept
Definition bitset.h:164
constexpr bool test(size_t i) const noexcept
Definition bitset.h:130
friend constexpr void swap(Bitset &b1, Bitset &b2) noexcept
Definition bitset.h:121
constexpr reference operator[](size_t i) noexcept
Definition bitset.h:135
friend constexpr Bitset operator-(Bitset b1, const Bitset &b2)
Definition bitset.h:227
constexpr Bitset() noexcept=default
constexpr Bitset & operator|=(const Bitset &other)
Definition bitset.h:196
constexpr iterator end() const noexcept
Definition bitset.h:260
constexpr iterator begin() const noexcept
Definition bitset.h:259
constexpr bool any() const noexcept
Definition bitset.h:185
constexpr bool intersects(const Bitset &other) const noexcept
Do this and other have at least one bit in common?
Definition bitset.h:249
constexpr size_t hash() const noexcept
Definition bitset.h:265
constexpr Bitset & clear() noexcept
Clears all bits and releases the heap storage again.
Definition bitset.h:169
static constexpr size_t Inline_Bits
Number of bits available without allocating.
Definition bitset.h:31
constexpr size_t count() const noexcept
Definition bitset.h:179
void dump() const
Definition bitset.h:289
constexpr bool operator==(const Bitset &other) const noexcept
Definition bitset.h:233
friend constexpr Bitset operator&(Bitset b1, const Bitset &b2)
Definition bitset.h:225
constexpr Bitset & set(size_t i, bool b)
Definition bitset.h:158
static constexpr size_t Bits_Per_Word
Number of bits in one word.
Definition bitset.h:30
constexpr Bitset(Bitset &&other) noexcept
Definition bitset.h:113
constexpr Bitset & operator=(Bitset other) noexcept
Definition bitset.h:117
constexpr Bitset & flip(size_t i)
Definition bitset.h:159
constexpr bool operator[](size_t i) const noexcept
Definition bitset.h:134
constexpr Bitset & operator-=(const Bitset &other) noexcept
Definition bitset.h:218
friend std::ostream & operator<<(std::ostream &os, const Bitset &bitset)
Definition bitset.h:291
static constexpr size_t npos
Returned by Bitset::next if there is no set bit.
Definition bitset.h:32
constexpr Bitset & set(size_t i)
Definition bitset.h:153
friend constexpr Bitset operator^(Bitset b1, const Bitset &b2)
Definition bitset.h:226
constexpr bool on_heap() const noexcept
Outgrown the inline storage?
Definition bitset.h:190
constexpr Bitset & operator&=(const Bitset &other) noexcept
Definition bitset.h:203
friend constexpr Bitset operator|(Bitset b1, const Bitset &b2)
Definition bitset.h:224
constexpr bool subset_of(const Bitset &other) const noexcept
Is every bit set in this also set in other?
Definition bitset.h:241
Definition algo.h:17
constexpr D bitcast_resize(const S &src) noexcept
A bitcast from src of type S to D, supporting different sizes.
Definition algo.h:26
constexpr size_t hash_begin() noexcept
Seeds a hash chain with the FNV-1 offset basis.
Definition hash.h:64
constexpr size_t hash_combine(size_t seed, T v) noexcept
Mixes v into seed word-wise, reusing the FNV-1 prime as multiplier.
Definition hash.h:68
Definition span.h:129
constexpr size_t operator()(const Bitset &bitset) const noexcept
Definition bitset.h:276