1 /** Bitarray. 2 */ 3 module nxt.container.bitarray; 4 5 @safe: 6 7 /** Array of bits. 8 * 9 * Like `std.bitmanip.BitArray` but pure nothrow @safe @nogc. 10 * 11 * Set `blockAlignedLength` to true if `this.length` is always a multiple of 12 * `Block.size`. 13 * 14 * TODO: use `Flag` instead, or wrap in `BlockAlignedBitArray` where this class 15 * is made private _BitArray and alias BitArray = _BitArray!(true). 16 * 17 * TODO: support append bit via `pushBack(bool)`. 18 */ 19 struct BitArray(bool blockAlignedLength = false, 20 alias Allocator = null) /* TODO: use Allocator */ 21 { 22 import core.bitop : bt, bts, btr; 23 import nxt.bitarray_algorithm; 24 25 pure nothrow @safe @nogc: 26 27 /** Helper constructor for `length` number of bits. */ 28 private this(size_t length) @trusted 29 in { 30 static if (blockAlignedLength) 31 assert(length % bitsPerBlock == 0, 32 "Parameter `length` is not a multiple `Block` bit size " ~ bitsPerBlock.stringof); 33 } do { 34 static if (blockAlignedLength) 35 _blockCount = length / bitsPerBlock; // number of whole blocks 36 else { 37 _blockCount = (length + bitsPerBlock-1) / bitsPerBlock; 38 _length = length; 39 } 40 _blockPtr = cast(Block*)fakePureCalloc(bitsPerBlock, _blockCount); /* TODO: use `Allocator` */ 41 } 42 43 /** Helper constructor. */ 44 private this(size_t length, const scope Block[] blocks) @trusted { 45 _blockCount = blocks.length; 46 _blockPtr = cast(Block*)fakePureMalloc(bitsPerBlock * _blockCount); /* TODO: use `Allocator` */ 47 _blocks[] = blocks; // copy block array 48 static if (!blockAlignedLength) { 49 _length = length; 50 } 51 } 52 53 /** Construct with `length` number of zero bits stored in `blocks`. */ 54 private static typeof(this) withLengthAndBlocks(size_t length, const scope Block[] blocks) 55 => typeof(return)(length, blocks); 56 57 /// Destroy. 58 ~this() nothrow @nogc => release(); 59 60 /// Explicit copying (duplicate). 61 typeof(this) dup() => typeof(this).withLengthAndBlocks(_length, _blocks); 62 63 /// Empty. 64 void clear() { 65 release(); 66 resetInternalData(); 67 } 68 69 /// Release internal store. 70 private void release() @trusted @nogc => fakePureFree(_blockPtr); 71 72 /// Reset internal data. 73 private void resetInternalData() { 74 _blockPtr = null; 75 _blockCount = 0; 76 static if (!blockAlignedLength) 77 _length = 0; 78 } 79 80 /// Set length. 81 @property void length(size_t newLength) { 82 if (newLength == length) 83 return; 84 auto that = typeof(this)(newLength); 85 that._blocks[0 .. _blocks.length] = this._blocks; 86 import std.algorithm.mutation : move; 87 this = move(that); 88 } 89 90 /// Get length. 91 @property size_t length() const => _length; 92 alias opDollar = length; /// ditto 93 94 /// Get capacity in number of bits. 95 @property size_t capacity() const => bitsPerBlock*_blockCount; 96 97 /** Get the `i`'th bit. */ 98 bool opIndex(size_t i) const @trusted { 99 version (D_Coverage) {} else pragma(inline, true); 100 assert(i < length); /* TODO: nothrow or not? */ 101 return cast(bool)bt(_blockPtr, i); 102 } 103 104 /** Set the `i`'th bit to `value`. */ 105 bool opIndexAssign(bool value, size_t i) @trusted { 106 version (D_Coverage) {} else pragma(inline, true); 107 if (value) 108 bts(_blockPtr, i); 109 else 110 btr(_blockPtr, i); 111 return value; 112 } 113 114 /** Set all bits to `value` via slice assignment syntax. */ 115 ref typeof(this) opSliceAssign(bool value) { 116 if (value) 117 one(); 118 else 119 zero(); 120 return this; 121 } 122 123 /** Clear all bits (to zero). */ 124 private void zero() { 125 foreach (ref block; _blocks) 126 block = Block.min; 127 } 128 129 /** Set all bits (to one). */ 130 private void one() { 131 foreach (ref block; _blocks) 132 block = Block.max; 133 } 134 135 version (none) /* TODO: activate? */ 136 bool opCast(T : bool)() const => !this.allZero; 137 138 /** Check if `this` has only zeros. */ 139 bool allZero()() const pure nothrow @safe @nogc { 140 foreach (const block; _fullBlocks) 141 if (block != Block.min) 142 return false; 143 static if (!blockAlignedLength) 144 if (_restBlockZeroPadded != Block.min) 145 return false; 146 return true; 147 } 148 149 /** Check if `this` has only ones. */ 150 bool allOne()() const pure nothrow @safe @nogc { 151 foreach (const block; _fullBlocks) 152 if (block != Block.max) 153 return false; 154 static if (!blockAlignedLength) 155 if (_restBlockOnePadded != Block.max) 156 return false; 157 return true; 158 } 159 160 /** Get number of bits set (to one). */ 161 size_t countOnes()() const /*tlm*/ 162 => nxt.bitarray_algorithm.countOnes!(const(Block)[], blockAlignedLength)(_blocks, length); 163 164 /** Get number of bits cleared (to zero). */ 165 size_t countZeros()() const /*tlm*/ 166 => length - countOnes; 167 168 /** Find index of first set (one) bit or `length` if no bit set. 169 * 170 * Optimized for ones-sparsity. 171 */ 172 size_t indexOfFirstOne()() const /*tlm*/ 173 => nxt.bitarray_algorithm.indexOfFirstOne!(const(Block)[], blockAlignedLength)(_blocks, length); 174 175 /** Find index of first cleared (zero) bit or `length` if no bit cleared. 176 * 177 * Optimized for zeros-sparsity. 178 */ 179 size_t indexOfFirstZero()() const /*tlm*/ 180 => nxt.bitarray_algorithm.indexOfFirstZero!(const(Block)[], blockAlignedLength)(_blocks, length); 181 182 /** Equality, operators == and !=. */ 183 bool opEquals(const scope ref typeof(this) rhs) const @trusted /* TODO: use `in ref` when it compiles */ 184 { 185 static if (!blockAlignedLength) 186 if (length != rhs.length) 187 return false; 188 if (_fullBlocks != rhs._fullBlocks) 189 return false; 190 static if (!blockAlignedLength) { 191 const restBitCount = length % bitsPerBlock; 192 if (restBitCount) 193 return _restBlockZeroPadded == rhs._restBlockZeroPadded; 194 } 195 return true; 196 } 197 198 /** Only explicit copying via `.dup` for now. */ 199 this(this) @disable; 200 201 private: 202 203 /** Get all blocks including the last being potentially not fully occupied. */ 204 inout(Block)[] _blocks() inout @trusted => _blockPtr[0 .. _blockCount]; 205 206 /** Get Blocks where all the bits are used (not ignored). */ 207 static if (blockAlignedLength) 208 inout(Block)[] _fullBlocks() inout @trusted => _blocks; // all bits of all blocks are used 209 else { 210 inout(Block)[] _fullBlocks() inout @trusted => _blocks.ptr[0 .. (length / bitsPerBlock)]; 211 /** Return rest `Block` with all padding bits set to zero. */ 212 Block _restBlockZeroPadded() const @trusted => _blocks[$-1] & ((1UL << (length % bitsPerBlock)) - 1); 213 } 214 215 alias Block = size_t; 216 enum bitsPerBlock = 8*Block.sizeof; /// Number of bits per `Block`. 217 218 /** Number of Block's allocated at `_blockPtr`. */ 219 size_t _blockCount; 220 221 static if (is(Allocator == std.experimental.allocator.gc_allocator.GCAllocator)) 222 Block* _blockPtr; // GC-allocated store pointer 223 else { 224 import nxt.gc_traits : NoGc; 225 @NoGc Block* _blockPtr; // non-GC-allocated store pointer 226 } 227 228 static if (blockAlignedLength) 229 @property size_t _length() const => bitsPerBlock * _blockCount; 230 else 231 size_t _length; 232 } 233 234 /// Test `_blockCount` and `_fullBlocks.length`. 235 pure nothrow @safe @nogc unittest { 236 static void test(bool blockAlignedLength)() { 237 import nxt.construction : makeOfLength; 238 alias BA = BitArray!(blockAlignedLength); 239 240 assert(makeOfLength!BA(0)._blockCount == 0); 241 assert(makeOfLength!BA(1)._blockCount == 1); 242 243 { 244 auto a = makeOfLength!BA(1*BA.bitsPerBlock - 1); 245 assert(a._blockCount == 1); 246 assert(a._fullBlocks.length == 0); 247 } 248 249 { 250 auto a = makeOfLength!BA(1*BA.bitsPerBlock + 0); 251 assert(a._blockCount == 1); 252 assert(a._fullBlocks.length == 1); 253 } 254 255 { 256 auto a = makeOfLength!BA(1*BA.bitsPerBlock + 1); 257 assert(a._blockCount == 2); 258 assert(a._fullBlocks.length == 1); 259 } 260 261 { 262 auto a = makeOfLength!BA(2*BA.bitsPerBlock - 1); 263 assert(a._blockCount == 2); 264 assert(a._fullBlocks.length == 1); 265 } 266 267 { 268 auto a = makeOfLength!BA(2*BA.bitsPerBlock + 0); 269 assert(a._blockCount == 2); 270 assert(a._fullBlocks.length == 2); 271 } 272 273 { 274 auto a = makeOfLength!BA(2*BA.bitsPerBlock + 1); 275 assert(a._blockCount == 3); 276 assert(a._fullBlocks.length == 2); 277 } 278 } 279 test!(false)(); 280 } 281 282 /// Test indexing and element assignment. 283 pure nothrow @safe @nogc unittest { 284 static void test(bool blockAlignedLength)(size_t length) { 285 alias BA = BitArray!(blockAlignedLength); 286 287 auto a = makeOfLength!BA(length); 288 289 assert(a.length == length); 290 foreach (const i; 0 .. length) 291 assert(!a[i]); 292 293 a[0] = true; 294 assert(a[0]); 295 foreach (const i; 1 .. length) 296 assert(!a[i]); 297 298 assert(!a[1]); 299 a[1] = true; 300 assert(a[1]); 301 a[1] = false; 302 assert(!a[1]); 303 } 304 test!(false)(100); 305 test!(true)(64); 306 } 307 308 /// Test `countOnes` and `countZeros`. 309 pure nothrow @safe @nogc unittest { 310 static void test(bool blockAlignedLength)() { 311 alias BA = BitArray!(blockAlignedLength); 312 313 foreach (const n; 1 .. 5*BA.bitsPerBlock) { 314 static if (blockAlignedLength) 315 if (n % BA.bitsPerBlock != 0) // if block aligned length 316 continue; 317 318 auto a = makeOfLength!BA(n); 319 320 // set bits forwards 321 foreach (const i; 0 .. n) { 322 assert(a.countOnes == i); 323 assert(a.countZeros == n - i); 324 a[i] = true; 325 assert(a.countOnes == i + 1); 326 assert(a.countZeros == n - (i + 1)); 327 } 328 329 assert(a.countOnes == n); 330 assert(a.countZeros == 0); 331 332 auto b = a.dup; 333 assert(b.countOnes == n); 334 assert(b.countZeros == 0); 335 336 assert(a == b); 337 338 // clear `a` bits forwards 339 foreach (const i; 0 .. n) { 340 assert(a.countOnes == n - i); 341 assert(a.countZeros == i); 342 a[i] = false; 343 assert(a.countOnes == n - (i + 1)); 344 assert(a.countZeros == i + 1); 345 } 346 347 b[] = false; 348 assert(a == b); 349 350 // set bits backwards 351 foreach (const i; 0 .. n) { 352 assert(a.countOnes == i); 353 a[n-1 - i] = true; 354 assert(a.countOnes == i + 1); 355 } 356 357 b[] = true; 358 assert(a == b); 359 } 360 } 361 test!(false)(); 362 test!(true)(); 363 } 364 365 /// Test emptying (resetting) via `.clear` and explicit copying with `.dup`. 366 pure nothrow @safe @nogc unittest { 367 static void test(bool blockAlignedLength)() { 368 alias BA = BitArray!(blockAlignedLength); 369 370 static if (blockAlignedLength) 371 const n = 5 * BA.bitsPerBlock; 372 else 373 const n = 5 * BA.bitsPerBlock + 1; 374 auto a = makeOfLength!BA(n); 375 376 assert(a.length == n); 377 378 a.clear(); 379 assert(a.length == 0); 380 381 a = makeOfLength!BA(n); 382 assert(a.length == n); 383 384 auto b = a.dup; 385 assert(b.length == n); 386 387 a.clear(); 388 assert(a.length == 0); 389 } 390 test!(false)(); 391 test!(true)(); 392 } 393 394 /// Test `indexOfFirstOne` for single set ones. 395 pure nothrow @safe @nogc unittest { 396 static void test(bool blockAlignedLength)() { 397 alias BA = BitArray!(blockAlignedLength); 398 399 static if (blockAlignedLength) 400 const n = 2 * BA.bitsPerBlock; 401 else 402 const n = 2 * BA.bitsPerBlock + 1; 403 auto a = makeOfLength!BA(n); 404 405 assert(a.length == n); 406 assert(a.indexOfFirstOne == n); // miss 407 408 a[0] = true; 409 assert(a.indexOfFirstOne == 0); 410 a[] = false; 411 412 a[2] = true; 413 assert(a.indexOfFirstOne == 2); 414 a[] = false; 415 416 a[n/2-1] = true; 417 assert(a.indexOfFirstOne == n/2-1); 418 a[] = false; 419 420 a[n/2] = true; 421 assert(a.indexOfFirstOne == n/2); 422 a[] = false; 423 424 a[n/2+1] = true; 425 assert(a.indexOfFirstOne == n/2+1); 426 a[] = false; 427 428 a[n-1] = true; 429 assert(a.indexOfFirstOne == n-1); 430 a[] = false; 431 432 assert(a.indexOfFirstOne == n); // miss 433 } 434 test!(false)(); 435 test!(true)(); 436 } 437 438 /// Test `indexOfFirstOne` for multi set ones. 439 pure nothrow @safe @nogc unittest { 440 static void test(bool blockAlignedLength)() { 441 alias BA = BitArray!(blockAlignedLength); 442 443 static if (blockAlignedLength) 444 const n = 2 * BA.bitsPerBlock; 445 else 446 const n = 2 * BA.bitsPerBlock + 1; 447 auto a = makeOfLength!BA(n); 448 449 a[0] = true; 450 a[BA.bitsPerBlock/2] = true; 451 a[BA.bitsPerBlock - 1] = true; 452 assert(a.indexOfFirstOne == 0); 453 } 454 test!(false)(); 455 test!(true)(); 456 } 457 458 /// Test `indexOfFirstZero` for single set zeros. 459 pure nothrow @safe @nogc unittest { 460 static void test(bool blockAlignedLength)() { 461 alias BA = BitArray!(blockAlignedLength); 462 463 static if (blockAlignedLength) 464 const n = 2 * BA.bitsPerBlock; 465 else 466 const n = 2 * BA.bitsPerBlock + 1; 467 auto a = makeOfLength!BA(n); 468 469 a[] = true; 470 471 assert(a.length == n); 472 assert(a.indexOfFirstZero == n); // miss 473 474 a[0] = false; 475 assert(a.indexOfFirstZero == 0); 476 a[0] = true; 477 478 a[2] = false; 479 assert(a.indexOfFirstZero == 2); 480 a[2] = true; 481 482 a[n/2-1] = false; 483 assert(a.indexOfFirstZero == n/2-1); 484 a[n/2-1] = true; 485 486 a[n/2] = false; 487 assert(a.indexOfFirstZero == n/2); 488 a[n/2] = true; 489 490 a[n/2+1] = false; 491 assert(a.indexOfFirstZero == n/2+1); 492 a[n/2+1] = true; 493 494 a[n-1] = false; 495 assert(a.indexOfFirstZero == n-1); 496 a[n-1] = true; 497 498 assert(a.indexOfFirstZero == n); // miss 499 } 500 test!(false)(); 501 test!(true)(); 502 } 503 504 pure nothrow @safe @nogc unittest { 505 alias BA = BitArray!(false); 506 static assert(BitArray!(false).sizeof == 3*BA.Block.sizeof); // one extra word for `length` 507 static assert(BitArray!(true).sizeof == 2*BA.Block.sizeof); 508 } 509 510 /// Test `indexOfFirstZero` for multi set zeros. 511 pure nothrow @safe @nogc unittest { 512 static void test(bool blockAlignedLength)() { 513 alias BA = BitArray!(blockAlignedLength); 514 515 static if (blockAlignedLength) 516 const n = 2 * BA.bitsPerBlock; 517 else 518 const n = 2 * BA.bitsPerBlock + 1; 519 auto a = makeOfLength!BA(n); 520 521 a[] = true; 522 523 a[0] = false; 524 a[BA.bitsPerBlock/2] = false; 525 a[BA.bitsPerBlock - 1] = false; 526 assert(a.indexOfFirstZero == 0); 527 } 528 test!(false)(); 529 test!(true)(); 530 } 531 532 /// Test `indexOfFirstOne` for multi set ones. 533 pure nothrow @safe @nogc unittest { 534 static void test(bool blockAlignedLength)() { 535 alias BA = BitArray!(blockAlignedLength); 536 537 static if (blockAlignedLength) 538 const n = 2 * BA.bitsPerBlock; 539 else 540 const n = 2 * BA.bitsPerBlock + 1; 541 auto a = makeOfLength!BA(n); 542 543 a[] = false; 544 545 a[0] = true; 546 a[BA.bitsPerBlock/2] = true; 547 a[BA.bitsPerBlock - 1] = true; 548 assert(a.indexOfFirstOne == 0); 549 } 550 test!(false)(); 551 test!(true)(); 552 } 553 554 /// Test casting to `bool`. 555 pure nothrow @safe @nogc unittest { 556 static void test(bool blockAlignedLength)() { 557 alias BA = BitArray!(blockAlignedLength); 558 559 static if (blockAlignedLength) 560 const n = 2 * BA.bitsPerBlock; 561 else 562 const n = 2 * BA.bitsPerBlock + 1; 563 auto a = makeOfLength!BA(n); 564 565 assert(a.allZero); 566 567 a[0] = true; 568 assert(!a.allZero); 569 a[0] = false; 570 assert(a.allZero); 571 } 572 test!(false)(); 573 } 574 575 /// 576 @trusted pure unittest { 577 import std.exception: assertThrown; 578 import core.exception : AssertError; 579 alias BA = BitArray!(true); 580 assertThrown!AssertError(makeOfLength!BA(1)); 581 } 582 583 extern (C) private pure @system @nogc nothrow { 584 pragma(mangle, "malloc") void* fakePureMalloc(size_t); 585 pragma(mangle, "calloc") void* fakePureCalloc(size_t nmemb, size_t size); 586 pragma(mangle, "realloc") void* fakePureRealloc(void* ptr, size_t size); 587 pragma(mangle, "free") void fakePureFree(void* ptr); 588 } 589 590 version (unittest) { 591 import nxt.construction : makeOfLength; 592 }