1 /** Bitarray. 2 */ 3 module nxt.bitarray; 4 5 @safe: 6 7 /** Array of bits. 8 * 9 * Like `std.bitmanip.BitArray` but @safe pure nothrow @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 @safe pure nothrow @nogc: 26 27 /** Construct with `length` number of zero bits. */ 28 static typeof(this) withLength(size_t length) 29 { 30 return typeof(this)(length); 31 } 32 33 /** Construct with `length` number of zero bits stored in `blocks`. */ 34 private static typeof(this) withLengthAndBlocks(size_t length, 35 const scope Block[] blocks) 36 { 37 return typeof(return)(length, blocks); 38 } 39 40 /** Helper constructor for `length` number of bits. */ 41 private this(size_t length) @trusted 42 { 43 static if (blockAlignedLength) 44 { 45 assert(length % bitsPerBlock == 0, 46 "Parameter `length` is not a multiple `Block` bit size " ~ bitsPerBlock.stringof); 47 _blockCount = length / bitsPerBlock; // number of whole blocks 48 } 49 else 50 { 51 _blockCount = (length + bitsPerBlock-1) / bitsPerBlock; 52 _length = length; 53 } 54 _blockPtr = cast(Block*)fakePureCalloc(bitsPerBlock, _blockCount); // TODO use `Allocator` 55 } 56 57 /** Helper constructor. */ 58 private this(size_t length, 59 const scope Block[] blocks) @trusted 60 { 61 _blockCount = blocks.length; 62 _blockPtr = cast(Block*)fakePureMalloc(bitsPerBlock * _blockCount); // TODO use `Allocator` 63 _blocks[] = blocks; // copy block array 64 static if (!blockAlignedLength) 65 { 66 _length = length; 67 } 68 } 69 70 /// Destroy. 71 ~this() @nogc 72 { 73 release(); 74 } 75 76 /// Explicit copying (duplicate). 77 typeof(this) dup() 78 { 79 return typeof(this).withLengthAndBlocks(_length, _blocks); 80 } 81 82 /// Empty. 83 void reset() 84 { 85 release(); 86 resetInternalData(); 87 } 88 alias clear = reset; 89 90 /// Release internal store. 91 private void release() @trusted @nogc 92 { 93 fakePureFree(_blockPtr); 94 } 95 96 /// Reset internal data. 97 private void resetInternalData() 98 { 99 _blockPtr = null; 100 _blockCount = 0; 101 static if (!blockAlignedLength) 102 { 103 _length = 0; 104 } 105 } 106 107 /// Check if empty. 108 bool empty() const { return _length == 0; } 109 110 /// Get length. 111 @property size_t length() const { return _length; } 112 alias opDollar = length; /// ditto 113 114 /// Get capacity in number of bits. 115 @property size_t capacity() const { return bitsPerBlock*_blockCount; } 116 117 /** Get the `i`'th bit. */ 118 bool opIndex(size_t i) const @trusted 119 { 120 version(D_Coverage) {} else pragma(inline, true); 121 assert(i < length); // TODO nothrow or not? 122 return cast(bool)bt(_blockPtr, i); 123 } 124 125 /** Set the `i`'th bit to `value`. */ 126 bool opIndexAssign(bool value, size_t i) @trusted 127 { 128 version(D_Coverage) {} else pragma(inline, true); 129 if (value) 130 { 131 bts(_blockPtr, i); 132 } 133 else 134 { 135 btr(_blockPtr, i); 136 } 137 return value; 138 } 139 140 /** Set all bits to `value` via slice assignment syntax. */ 141 ref typeof(this) opSliceAssign(bool value) 142 { 143 if (value) 144 { 145 one(); 146 } 147 else 148 { 149 zero(); 150 } 151 return this; 152 } 153 154 /** Clear all bits (to zero). */ 155 private void zero() 156 { 157 foreach (ref block; _blocks) 158 { 159 block = Block.min; 160 } 161 } 162 163 /** Set all bits (to one). */ 164 private void one() 165 { 166 foreach (ref block; _blocks) 167 { 168 block = Block.max; 169 } 170 } 171 172 version(none) // TODO activate? 173 bool opCast(T : bool)() const 174 { 175 return !this.allZero; 176 } 177 178 /** Check if `this` has only zeros. */ 179 bool allZero()() const @safe pure nothrow @nogc 180 { 181 foreach (const block; _fullBlocks) 182 { 183 if (block != Block.min) 184 { 185 return false; 186 } 187 } 188 static if (!blockAlignedLength) 189 { 190 if (_restBlockZeroPadded != Block.min) 191 { 192 return false; 193 } 194 } 195 return true; 196 } 197 198 /** Check if `this` has only ones. */ 199 bool allOne()() const @safe pure nothrow @nogc 200 { 201 foreach (const block; _fullBlocks) 202 { 203 if (block != Block.max) 204 { 205 return false; 206 } 207 } 208 static if (!blockAlignedLength) 209 { 210 if (_restBlockOnePadded != Block.max) 211 { 212 return false; 213 } 214 } 215 return true; 216 } 217 218 /** Get number of bits set (to one). */ 219 size_t countOnes()() const // template-lazy 220 { 221 return nxt.bitarray_algorithm.countOnes!(const(Block)[], blockAlignedLength)(_blocks, length); 222 } 223 224 /** Get number of bits cleared (to zero). */ 225 size_t countZeros()() const // template-lazy 226 { 227 return length - countOnes; 228 } 229 230 /** Find index of first set (one) bit or `length` if no bit set. 231 * 232 * Optimized for ones-sparsity. 233 */ 234 size_t indexOfFirstOne()() const // template-lazy 235 { 236 return nxt.bitarray_algorithm.indexOfFirstOne!(const(Block)[], blockAlignedLength)(_blocks, length); 237 } 238 239 /** Find index of first cleared (zero) bit or `length` if no bit cleared. 240 * 241 * Optimized for zeros-sparsity. 242 */ 243 size_t indexOfFirstZero()() const // template-lazy 244 { 245 return nxt.bitarray_algorithm.indexOfFirstZero!(const(Block)[], blockAlignedLength)(_blocks, length); 246 } 247 248 /** Equality, operators == and !=. */ 249 bool opEquals(in ref typeof(this) rhs) const @trusted 250 { 251 static if (!blockAlignedLength) 252 { 253 if (length != rhs.length) 254 { 255 return false; 256 } 257 } 258 if (_fullBlocks != rhs._fullBlocks) 259 { 260 return false; 261 } 262 static if (!blockAlignedLength) 263 { 264 const restBitCount = length % bitsPerBlock; 265 if (restBitCount) 266 { 267 return _restBlockZeroPadded == rhs._restBlockZeroPadded; 268 } 269 } 270 return true; 271 } 272 273 /** Only explicit copying via `.dup` for now. */ 274 @disable this(this); 275 276 private: 277 278 /** Get blocks. */ 279 inout(Block)[] _blocks() inout @trusted 280 { 281 return _blockPtr[0 .. _blockCount]; 282 } 283 284 static if (blockAlignedLength) 285 { 286 inout(Block)[] _fullBlocks() inout @trusted 287 { 288 return _blocks; // all bits of all blocks are used 289 } 290 } 291 else 292 { 293 inout(Block)[] _fullBlocks() inout @trusted 294 { 295 version(D_Coverage) {} else pragma(inline, true); 296 const fullBlockCount = length / bitsPerBlock; 297 return _blocks.ptr[0 .. fullBlockCount]; 298 } 299 300 /** Return rest `Block` with all padding bits set to zero. */ 301 Block _restBlockZeroPadded() const @trusted 302 { 303 const restBitCount = length % bitsPerBlock; 304 return _blocks[$-1] & ((1UL << restBitCount) - 1); 305 } 306 } 307 308 alias Block = size_t; 309 enum bitsPerBlock = 8*Block.sizeof; /// Number of bits per `Block`. 310 311 /** Number of Block's allocated at `_blockPtr`. */ 312 size_t _blockCount; 313 314 static if (is(Allocator == std.experimental.allocator.gc_allocator.GCAllocator)) 315 { 316 Block* _blockPtr; // GC-allocated store pointer 317 } 318 else 319 { 320 import nxt.gc_traits : NoGc; 321 @NoGc Block* _blockPtr; // non-GC-allocated store pointer 322 } 323 324 static if (blockAlignedLength) 325 { 326 @property size_t _length() const 327 { 328 return bitsPerBlock * _blockCount; 329 } 330 } 331 else 332 { 333 size_t _length; 334 } 335 } 336 337 /// Test `_blockCount` and `_fullBlocks.length`. 338 @safe pure nothrow @nogc unittest 339 { 340 static void test(bool blockAlignedLength)() 341 { 342 alias BA = BitArray!(blockAlignedLength); 343 344 assert(BA.withLength(0)._blockCount == 0); 345 assert(BA.withLength(1)._blockCount == 1); 346 347 { 348 auto a = BA.withLength(1*BA.bitsPerBlock - 1); 349 assert(a._blockCount == 1); 350 assert(a._fullBlocks.length == 0); 351 } 352 353 { 354 auto a = BA.withLength(1*BA.bitsPerBlock + 0); 355 assert(a._blockCount == 1); 356 assert(a._fullBlocks.length == 1); 357 } 358 359 { 360 auto a = BA.withLength(1*BA.bitsPerBlock + 1); 361 assert(a._blockCount == 2); 362 assert(a._fullBlocks.length == 1); 363 } 364 365 { 366 auto a = BA.withLength(2*BA.bitsPerBlock - 1); 367 assert(a._blockCount == 2); 368 assert(a._fullBlocks.length == 1); 369 } 370 371 { 372 auto a = BA.withLength(2*BA.bitsPerBlock + 0); 373 assert(a._blockCount == 2); 374 assert(a._fullBlocks.length == 2); 375 } 376 377 { 378 auto a = BA.withLength(2*BA.bitsPerBlock + 1); 379 assert(a._blockCount == 3); 380 assert(a._fullBlocks.length == 2); 381 } 382 } 383 test!(false)(); 384 } 385 386 /// Test indexing and element assignment. 387 @safe pure nothrow @nogc unittest 388 { 389 static void test(bool blockAlignedLength)(size_t length) 390 { 391 alias BA = BitArray!(blockAlignedLength); 392 393 auto a = BA.withLength(length); 394 395 assert(a.length == length); 396 foreach (const i; 0 .. length) 397 { 398 assert(!a[i]); 399 } 400 401 a[0] = true; 402 assert(a[0]); 403 foreach (const i; 1 .. length) 404 { 405 assert(!a[i]); 406 } 407 408 assert(!a[1]); 409 a[1] = true; 410 assert(a[1]); 411 a[1] = false; 412 assert(!a[1]); 413 } 414 test!(false)(100); 415 test!(true)(64); 416 } 417 418 /// Test `countOnes` and `countZeros`. 419 @safe pure nothrow @nogc unittest 420 { 421 static void test(bool blockAlignedLength)() 422 { 423 alias BA = BitArray!(blockAlignedLength); 424 425 foreach (const n; 1 .. 5*BA.bitsPerBlock) 426 { 427 static if (blockAlignedLength) 428 { 429 if (n % BA.bitsPerBlock != 0) // if block aligned length 430 { 431 continue; 432 } 433 } 434 435 auto a = BA.withLength(n); 436 437 // set bits forwards 438 foreach (const i; 0 .. n) 439 { 440 assert(a.countOnes == i); 441 assert(a.countZeros == n - i); 442 a[i] = true; 443 assert(a.countOnes == i + 1); 444 assert(a.countZeros == n - (i + 1)); 445 } 446 447 assert(a.countOnes == n); 448 assert(a.countZeros == 0); 449 450 auto b = a.dup; 451 assert(b.countOnes == n); 452 assert(b.countZeros == 0); 453 454 assert(a == b); 455 456 // clear `a` bits forwards 457 foreach (const i; 0 .. n) 458 { 459 assert(a.countOnes == n - i); 460 assert(a.countZeros == i); 461 a[i] = false; 462 assert(a.countOnes == n - (i + 1)); 463 assert(a.countZeros == i + 1); 464 } 465 466 b[] = false; 467 assert(a == b); 468 469 // set bits backwards 470 foreach (const i; 0 .. n) 471 { 472 assert(a.countOnes == i); 473 a[n-1 - i] = true; 474 assert(a.countOnes == i + 1); 475 } 476 477 b[] = true; 478 assert(a == b); 479 } 480 } 481 test!(false)(); 482 test!(true)(); 483 } 484 485 /// Test emptying (resetting) via `.clear` and explicit copying with `.dup`. 486 @safe pure nothrow @nogc unittest 487 { 488 static void test(bool blockAlignedLength)() 489 { 490 alias BA = BitArray!(blockAlignedLength); 491 492 static if (blockAlignedLength) 493 { 494 const n = 5 * BA.bitsPerBlock; 495 } 496 else 497 { 498 const n = 5 * BA.bitsPerBlock + 1; 499 } 500 auto a = BA.withLength(n); 501 502 assert(a.length == n); 503 504 a.reset(); 505 assert(a.length == 0); 506 507 a = BA.withLength(n); 508 assert(a.length == n); 509 510 auto b = a.dup; 511 assert(b.length == n); 512 513 a.reset(); 514 assert(a.length == 0); 515 } 516 test!(false)(); 517 test!(true)(); 518 } 519 520 /// Test `indexOfFirstOne` for single set ones. 521 @safe pure nothrow @nogc unittest 522 { 523 static void test(bool blockAlignedLength)() 524 { 525 alias BA = BitArray!(blockAlignedLength); 526 527 static if (blockAlignedLength) 528 { 529 const n = 2 * BA.bitsPerBlock; 530 } 531 else 532 { 533 const n = 2 * BA.bitsPerBlock + 1; 534 } 535 auto a = BA.withLength(n); 536 537 assert(a.length == n); 538 assert(a.indexOfFirstOne == n); // miss 539 540 a[0] = true; 541 assert(a.indexOfFirstOne == 0); 542 a[] = false; 543 544 a[2] = true; 545 assert(a.indexOfFirstOne == 2); 546 a[] = false; 547 548 a[n/2-1] = true; 549 assert(a.indexOfFirstOne == n/2-1); 550 a[] = false; 551 552 a[n/2] = true; 553 assert(a.indexOfFirstOne == n/2); 554 a[] = false; 555 556 a[n/2+1] = true; 557 assert(a.indexOfFirstOne == n/2+1); 558 a[] = false; 559 560 a[n-1] = true; 561 assert(a.indexOfFirstOne == n-1); 562 a[] = false; 563 564 assert(a.indexOfFirstOne == n); // miss 565 } 566 test!(false)(); 567 test!(true)(); 568 } 569 570 /// Test `indexOfFirstOne` for multi set ones. 571 @safe pure nothrow @nogc unittest 572 { 573 static void test(bool blockAlignedLength)() 574 { 575 alias BA = BitArray!(blockAlignedLength); 576 577 static if (blockAlignedLength) 578 { 579 const n = 2 * BA.bitsPerBlock; 580 } 581 else 582 { 583 const n = 2 * BA.bitsPerBlock + 1; 584 } 585 auto a = BA.withLength(n); 586 587 a[0] = true; 588 a[BA.bitsPerBlock/2] = true; 589 a[BA.bitsPerBlock - 1] = true; 590 assert(a.indexOfFirstOne == 0); 591 } 592 test!(false)(); 593 test!(true)(); 594 } 595 596 /// Test `indexOfFirstZero` for single set zeros. 597 @safe pure nothrow @nogc unittest 598 { 599 static void test(bool blockAlignedLength)() 600 { 601 alias BA = BitArray!(blockAlignedLength); 602 603 static if (blockAlignedLength) 604 { 605 const n = 2 * BA.bitsPerBlock; 606 } 607 else 608 { 609 const n = 2 * BA.bitsPerBlock + 1; 610 } 611 auto a = BA.withLength(n); 612 613 a[] = true; 614 615 assert(a.length == n); 616 assert(a.indexOfFirstZero == n); // miss 617 618 a[0] = false; 619 assert(a.indexOfFirstZero == 0); 620 a[0] = true; 621 622 a[2] = false; 623 assert(a.indexOfFirstZero == 2); 624 a[2] = true; 625 626 a[n/2-1] = false; 627 assert(a.indexOfFirstZero == n/2-1); 628 a[n/2-1] = true; 629 630 a[n/2] = false; 631 assert(a.indexOfFirstZero == n/2); 632 a[n/2] = true; 633 634 a[n/2+1] = false; 635 assert(a.indexOfFirstZero == n/2+1); 636 a[n/2+1] = true; 637 638 a[n-1] = false; 639 assert(a.indexOfFirstZero == n-1); 640 a[n-1] = true; 641 642 assert(a.indexOfFirstZero == n); // miss 643 } 644 test!(false)(); 645 test!(true)(); 646 } 647 648 @safe pure nothrow @nogc unittest 649 { 650 alias BA = BitArray!(false); 651 static assert(BitArray!(false).sizeof == 3*BA.Block.sizeof); // one extra word for `length` 652 static assert(BitArray!(true).sizeof == 2*BA.Block.sizeof); 653 } 654 655 /// Test `indexOfFirstZero` for multi set zeros. 656 @safe pure nothrow @nogc unittest 657 { 658 static void test(bool blockAlignedLength)() 659 { 660 alias BA = BitArray!(blockAlignedLength); 661 662 static if (blockAlignedLength) 663 { 664 const n = 2 * BA.bitsPerBlock; 665 } 666 else 667 { 668 const n = 2 * BA.bitsPerBlock + 1; 669 } 670 auto a = BA.withLength(n); 671 672 a[] = true; 673 674 a[0] = false; 675 a[BA.bitsPerBlock/2] = false; 676 a[BA.bitsPerBlock - 1] = false; 677 assert(a.indexOfFirstZero == 0); 678 } 679 test!(false)(); 680 test!(true)(); 681 } 682 683 /// Test `indexOfFirstOne` for multi set ones. 684 @safe pure nothrow @nogc unittest 685 { 686 static void test(bool blockAlignedLength)() 687 { 688 alias BA = BitArray!(blockAlignedLength); 689 690 static if (blockAlignedLength) 691 { 692 const n = 2 * BA.bitsPerBlock; 693 } 694 else 695 { 696 const n = 2 * BA.bitsPerBlock + 1; 697 } 698 auto a = BA.withLength(n); 699 700 a[] = false; 701 702 a[0] = true; 703 a[BA.bitsPerBlock/2] = true; 704 a[BA.bitsPerBlock - 1] = true; 705 assert(a.indexOfFirstOne == 0); 706 } 707 test!(false)(); 708 test!(true)(); 709 } 710 711 /// Test casting to `bool`. 712 @safe pure nothrow @nogc unittest 713 { 714 static void test(bool blockAlignedLength)() 715 { 716 alias BA = BitArray!(blockAlignedLength); 717 718 static if (blockAlignedLength) 719 { 720 const n = 2 * BA.bitsPerBlock; 721 } 722 else 723 { 724 const n = 2 * BA.bitsPerBlock + 1; 725 } 726 auto a = BA.withLength(n); 727 728 assert(a.allZero); 729 730 a[0] = true; 731 assert(!a.allZero); 732 a[0] = false; 733 assert(a.allZero); 734 } 735 test!(false)(); 736 } 737 738 /// 739 @trusted pure unittest 740 { 741 import std.exception: assertThrown; 742 import core.exception : AssertError; 743 assertThrown!AssertError(BitArray!(true).withLength(1)); 744 } 745 746 // TODO use 747 extern (C) private pure @system @nogc nothrow 748 { 749 pragma(mangle, "malloc") void* fakePureMalloc(size_t); 750 pragma(mangle, "calloc") void* fakePureCalloc(size_t nmemb, size_t size); 751 pragma(mangle, "realloc") void* fakePureRealloc(void* ptr, size_t size); 752 pragma(mangle, "free") void fakePureFree(void* ptr); 753 }