1 /** Statically sized variant of `std.bitmanip.BitArray. 2 * 3 * Copyright: Per Nordlöw 2022-. 4 * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 * Authors: $(WEB Per Nordlöw) 6 */ 7 module nxt.container.static_bitarray; 8 9 @safe: 10 11 alias DefaultBlock = size_t; ///< Default block type. 12 13 import std.traits : isUnsigned; 14 15 /** A statically sized `std.bitmanip.BitArray`. 16 * 17 * TODO: Infer `Block` from `len` as is done for `Bound` and `Mod`. 18 * 19 * TODO: Optimize `allOne`, `allZero` using intrinsic? 20 */ 21 struct StaticBitArray(uint capacity, Block = DefaultBlock) 22 if (isUnsigned!DefaultBlock) { 23 @safe: 24 import std.format : FormatSpec, format; 25 import nxt.modulo : Mod; 26 27 /** Number of bits. 28 * 29 * Length equals capacity. 30 */ 31 enum length = capacity; 32 33 static if (capacity >= 1) 34 alias Index = Mod!capacity; 35 36 static if (Block.sizeof == 8) 37 import core.bitop : bt, bts, btr; 38 else 39 import nxt.bitop_ex : bt, bts, btr; 40 41 /** Number of bits per `Block`. */ 42 enum bitsPerBlock = 8*Block.sizeof; 43 /** Number of `Block`s. */ 44 enum blockCount = (capacity + bitsPerBlock-1) / bitsPerBlock; 45 46 /** Data stored as `Block`s. */ 47 private Block[blockCount] _blocks; 48 49 /** Data as an array of unsigned bytes. */ 50 pragma(inline, true) 51 inout(ubyte)[] ubytes()() inout @trusted => (cast(ubyte*)&_blocks)[0 .. _blocks.sizeof]; 52 53 /** Get pointer to data blocks. */ 54 pragma(inline, true) 55 @property inout(Block*) ptr() inout @system => _blocks.ptr; 56 57 /** Reset all bits (to zero). */ 58 pragma(inline, true) 59 void reset()() { 60 _blocks[] = 0; /+ TODO: is this fastest way? +/ 61 } 62 alias clear = reset; 63 64 /** Gets the amount of native words backing `this`. */ 65 pragma(inline, true) 66 @property static uint dim() => blockCount; 67 68 /** Bidirectional range into `BitArray`. 69 * 70 * TODO: Provide opSliceAssign for interopability with range algorithms via 71 * private static struct member `Range`. 72 * 73 * TODO: Look at how std.container.array implements this. 74 * 75 * See_Also: https://dlang.org/phobos/std_bitmanip.html#bitsSet 76 */ 77 struct Range() /*tlm*/ 78 { 79 pure nothrow @safe @nogc: 80 pragma(inline, true): 81 82 /// Returns: `true` iff `this` is empty. 83 bool empty() const => _i == _j; 84 85 /// Returns: `this` length. 86 size_t length() const => _j - _i; 87 88 import std.traits : isMutable; 89 static if (isMutable!(typeof(_store))) 90 this(this) @disable; // Rust-style mutable reference semantics 91 92 bool front() const in(!empty) => (*_store)[_i]; 93 bool back() const in(!empty) => (*_store)[_j - 1]; 94 void popFront() in(!empty) { ++_i; } 95 void popBack() in(!empty) { --_j; } 96 97 private: 98 StaticBitArray* _store; 99 size_t _i = 0; // front iterator into _store 100 size_t _j = _store.length; // back iterator into _store 101 } 102 103 pragma(inline, true) 104 scope inout(Range!()) opSlice()() inout return @trusted => typeof(return)(&this); 105 pragma(inline, true) 106 scope inout(Range!()) opSlice()(in size_t i, size_t j) inout return @trusted => typeof(return)(&this, i, j); 107 108 /** Set all bits to `value` via slice assignment syntax. */ 109 ref typeof(this) opSliceAssign(bool value) { 110 if (value) 111 one(); 112 else 113 zero(); 114 return this; 115 } 116 117 /** Set all bits (to zero). */ 118 private void zero() { 119 foreach (ref block; _blocks) 120 block = Block.min; 121 } 122 123 /** Set all bits (to one). */ 124 private void one() { 125 foreach (ref block; _blocks) 126 block = Block.max; 127 } 128 129 /** Gets the $(D i)'th bit. */ 130 pragma(inline, true) 131 bool opIndex(in size_t i) const @trusted 132 in(i < capacity) /+ TODO: nothrow or not? +/ 133 { 134 // Andrei: review for @@@64-bit@@@ 135 static if (Block.sizeof == 8) 136 return cast(bool)bt(ptr, i); 137 else 138 return bt(_blocks[i/bitsPerBlock], i%bitsPerBlock); 139 } 140 141 /** Gets the $(D i)'th bit. No range checking needed. */ 142 static if (capacity >= 1) { 143 /** Get the $(D i)'th bit. 144 * 145 * Avoids range-checking because `i` of type is bound to (0 .. capacity-1). 146 */ 147 bool opIndex(ModUInt)(Mod!(capacity, ModUInt) i) const @trusted 148 if (isUnsigned!ModUInt) { 149 pragma(inline, true); 150 static if (Block.sizeof == 8) 151 return cast(bool)bt(ptr, cast(size_t)i); 152 else 153 return bt(_blocks[i/bitsPerBlock], i%bitsPerBlock); 154 } 155 156 /** Get the $(D i)'th bit. 157 * 158 * Statically verifies that i is < StaticBitArray length. 159 */ 160 pragma(inline, true) 161 bool at(size_t i)() const if (i < capacity) => this[i]; 162 } 163 164 /** Puts the $(D i)'th bit to $(D b). */ 165 pragma(inline, true) 166 void put()(in size_t i, bool b) @trusted { this[i] = b; } 167 168 /** Sets the $(D i)'th bit. */ 169 import std.traits : isIntegral; 170 bool opIndexAssign(Index2)(bool b, Index2 i) @trusted 171 if (isIntegral!Index2) 172 in(i < capacity) 173 in 174 { 175 // import std.traits: isMutable; 176 // See_Also: http://stackoverflow.com/questions/19906516/static-parameter-function-specialization-in-d 177 /* static if (!isMutable!Index2) { */ 178 /* import std.conv: to; */ 179 /* static assert(i < capacity, */ 180 /* "Index2 " ~ to!string(i) ~ " must be smaller than StaticBitArray length " ~ to!string(capacity)); */ 181 /* } */ 182 } 183 do 184 { 185 pragma(inline, true); 186 if (b) 187 bts(ptr, cast(size_t)i); 188 else 189 btr(ptr, cast(size_t)i); 190 return b; 191 } 192 193 static if (capacity >= 1) { 194 /** Sets the $(D i)'th bit. No range checking needed. */ 195 pragma(inline, true) 196 bool opIndexAssign(ModUInt)(bool b, Mod!(capacity, ModUInt) i) @trusted 197 if (isUnsigned!ModUInt) { 198 if (b) 199 bts(ptr, cast(size_t)i); 200 else 201 btr(ptr, cast(size_t)i); 202 return b; 203 } 204 } 205 206 /// 207 pure nothrow @safe @nogc unittest 208 { 209 StaticBitArray!2 bs; 210 bs[0] = true; 211 assert(bs[0]); 212 assert(!bs[1]); 213 bs[1] = true; 214 assert(bs[1]); 215 } 216 217 /** Support for $(D foreach) loops for $(D StaticBitArray). */ 218 int opApply(scope int delegate(ref bool) dg) @trusted 219 { 220 int result; 221 foreach (const size_t i; 0 .. capacity) { 222 bool b = opIndex(i); 223 result = dg(b); 224 this[i] = b; 225 if (result) { break; } 226 } 227 return result; 228 } 229 230 /** ditto */ 231 int opApply(scope int delegate(bool) dg) const @trusted 232 { 233 int result; 234 foreach (const size_t i; 0 .. capacity) { 235 bool b = opIndex(i); 236 result = dg(b); 237 if (result) { break; } 238 } 239 return result; 240 } 241 242 /** ditto */ 243 int opApply(scope int delegate(const ref size_t, ref bool) dg) @trusted 244 { 245 int result; 246 foreach (const size_t i; 0 .. capacity) { 247 bool b = opIndex(i); 248 result = dg(i, b); 249 this[i] = b; 250 if (result) { break; } 251 } 252 return result; 253 } 254 255 /** ditto */ 256 int opApply(scope int delegate(in size_t, bool) dg) const @trusted 257 { 258 int result; 259 foreach (const size_t i; 0 .. capacity) { 260 bool b = opIndex(i); 261 result = dg(i, b); 262 if (result) { break; } 263 } 264 return result; 265 } 266 267 /// 268 unittest 269 { 270 static bool[] ba = [1,0,1]; 271 auto a = StaticBitArray!3(ba); 272 size_t i; 273 foreach (immutable b; a[]) /+ TODO: is `opSlice` the right thing? +/ 274 { 275 switch (i) { 276 case 0: assert(b == true); break; 277 case 1: assert(b == false); break; 278 case 2: assert(b == true); break; 279 default: assert(0); 280 } 281 i++; 282 } 283 foreach (j, b; a) /+ TODO: is `opSlice` the right thing? +/ 284 { 285 switch (j) { 286 case 0: assert(b == true); break; 287 case 1: assert(b == false); break; 288 case 2: assert(b == true); break; 289 default: assert(0); 290 } 291 } 292 } 293 294 /** Reverse block `Block`. */ 295 static @property Block reverseBlock()(in Block block) { 296 import core.bitop : bitswap; 297 pragma(inline, true); 298 static if (Block.sizeof == 4) 299 return cast(uint)block.bitswap; 300 else static if (Block.sizeof == 8) 301 return (((cast(Block)((cast(uint)(block)).bitswap)) << 32) | 302 (cast(Block)((cast(uint)(block >> 32)).bitswap))); 303 else 304 return block; 305 } 306 307 /** Reverses the bits of the $(D StaticBitArray) in place. */ 308 @property typeof(this) reverse()() 309 out (result) { 310 assert(result == this); 311 } 312 do 313 { 314 static if (length == blockCount * bitsPerBlock) { 315 static if (blockCount == 1) 316 _blocks[0] = reverseBlock(_blocks[0]); 317 else static if (blockCount == 2) { 318 const tmp = _blocks[1]; 319 _blocks[1] = reverseBlock(_blocks[0]); 320 _blocks[0] = reverseBlock(tmp); 321 } 322 else static if (blockCount == 3) { 323 const tmp = _blocks[2]; 324 _blocks[2] = reverseBlock(_blocks[0]); 325 _blocks[1] = reverseBlock(_blocks[1]); 326 _blocks[0] = reverseBlock(tmp); 327 } 328 else 329 { 330 size_t lo = 0; 331 size_t hi = _blocks.length - 1; 332 for (; lo < hi; ++lo, --hi) { 333 immutable t = reverseBlock(_blocks[lo]); 334 _blocks[lo] = reverseBlock(_blocks[hi]); 335 _blocks[hi] = t; 336 } 337 if (lo == hi) 338 _blocks[lo] = reverseBlock(_blocks[lo]); 339 } 340 } 341 else 342 { 343 static if (length >= 2) { 344 size_t lo = 0; 345 size_t hi = capacity - 1; 346 for (; lo < hi; ++lo, --hi) { 347 immutable t = this[lo]; 348 this[lo] = this[hi]; 349 this[hi] = t; 350 } 351 } 352 } 353 return this; 354 } 355 356 /// 357 pure unittest 358 { 359 enum capacity = 64; 360 static immutable bool[capacity] data = [0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 361 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0]; 362 auto b = StaticBitArray!capacity(data); 363 b.reverse(); 364 foreach (const i; 0 .. data.length) 365 assert(b[i] == data[capacity - 1 - i]); 366 } 367 368 /// 369 pure unittest 370 { 371 enum capacity = 64*2; 372 static immutable bool[capacity] data = [0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 373 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 374 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 375 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0]; 376 auto b = StaticBitArray!capacity(data); 377 b.reverse(); 378 foreach (const i; 0 .. data.length) 379 assert(b[i] == data[capacity - 1 - i]); 380 } 381 382 /// 383 pure unittest 384 { 385 enum capacity = 64*3; 386 static immutable bool[capacity] data = [0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 387 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 388 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 389 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 390 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 391 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0, 0,1,1,0,1,0,1,0]; 392 auto b = StaticBitArray!capacity(data); 393 b.reverse(); 394 foreach (const i; 0 .. data.length) 395 assert(b[i] == data[capacity - 1 - i]); 396 } 397 398 /** Sorts the $(D StaticBitArray)'s elements. */ 399 @property typeof(this) sort()() 400 in(result == this) 401 out(result) { 402 if (capacity >= 2) { 403 size_t lo, hi; 404 lo = 0; 405 hi = capacity - 1; 406 while (1) { 407 while (1) { 408 if (lo >= hi) 409 goto Ldone; 410 if (this[lo] == true) 411 break; 412 lo++; 413 } 414 while (1) { 415 if (lo >= hi) 416 goto Ldone; 417 if (this[hi] == false) 418 break; 419 hi--; 420 } 421 this[lo] = false; 422 this[hi] = true; 423 lo++; 424 hi--; 425 } 426 } 427 Ldone: 428 return this; 429 } 430 431 /* unittest */ 432 /* { */ 433 /* __gshared size_t x = 0b1100011000; */ 434 /* __gshared StaticBitArray ba = { 10, &x }; */ 435 /* ba.sort(); */ 436 /* for (size_t i = 0; i < 6; ++i) */ 437 /* assert(ba[i] == false); */ 438 /* for (size_t i = 6; i < 10; ++i) */ 439 /* assert(ba[i] == true); */ 440 /* } */ 441 442 443 /** Support for operators == and != for $(D StaticBitArray). */ 444 bool opEquals(Block2)(in StaticBitArray!(capacity, Block2) a2) const @trusted 445 if (isUnsigned!Block2) { 446 size_t i; 447 448 if (this.length != a2.length) { return 0; } // not equal 449 auto p1 = this.ptr; 450 auto p2 = a2.ptr; 451 auto n = this.length / bitsPerBlock; 452 for (i = 0; i < n; ++i) 453 if (p1[i] != p2[i]) 454 return 0; // not equal 455 456 n = this.length & (bitsPerBlock-1); 457 size_t mask = (1 << n) - 1; 458 //printf("i = %d, n = %d, mask = %x, %x, %x\n", i, n, mask, p1[i], p2[i]); 459 return (mask == 0) || (p1[i] & mask) == (p2[i] & mask); 460 } 461 /// 462 nothrow unittest 463 { 464 auto a = StaticBitArray!(5, ubyte)([1,0,1,0,1]); 465 auto b = StaticBitArray!(5, ushort)([1,0,1,1,1]); 466 auto c = StaticBitArray!(5, uint)([1,0,1,0,1]); 467 auto d = StaticBitArray!(5, ulong)([1,1,1,1,1]); 468 assert(a != b); 469 assert(a == c); 470 assert(a != d); 471 } 472 473 /** Supports comparison operators for $(D StaticBitArray). */ 474 int opCmp(Block2)(in StaticBitArray!(capacity, Block2) a2) const @trusted 475 if (isUnsigned!Block2) { 476 uint i; 477 478 auto capacity = this.length; 479 if (a2.length < capacity) { capacity = a2.length; } 480 auto p1 = this.ptr; 481 auto p2 = a2.ptr; 482 auto n = capacity / bitsPerBlock; 483 for (i = 0; i < n; ++i) 484 if (p1[i] != p2[i]) { break; } // not equal 485 for (size_t j = 0; j < capacity-i * bitsPerBlock; j++) { 486 size_t mask = cast(size_t)(1 << j); 487 auto c = (cast(long)(p1[i] & mask) - cast(long)(p2[i] & mask)); 488 if (c) { return c > 0 ? 1 : -1; } 489 } 490 return cast(int)this.length - cast(int)a2.length; 491 } 492 493 /// 494 nothrow unittest 495 { 496 auto a = StaticBitArray!(5, ubyte)([1,0,1,0,1]); 497 auto b = StaticBitArray!(5, ushort)([1,0,1,1,1]); 498 auto c = StaticBitArray!(5, uint)([1,0,1,0,1]); 499 auto d = StaticBitArray!(5, ulong)([1,1,1,1,1]); 500 assert(a < b); 501 assert(a <= b); 502 assert(a == c); 503 assert(a <= c); 504 assert(a >= c); 505 assert(c < d); 506 } 507 508 /** Support for hashing for $(D StaticBitArray). */ 509 extern(D) hash_t toHash() const @trusted pure nothrow 510 { 511 typeof(return) hash = 3557; 512 auto n = capacity / 8; 513 foreach (const i; 0 .. n) { 514 hash *= 3559; 515 hash += (cast(byte*)this.ptr)[i]; 516 } 517 for (size_t i = 8*n; i < capacity; ++i) { 518 hash *= 3571; 519 hash += this[i]; 520 } 521 return hash; 522 } 523 524 /** Set `this` to the contents of $(D ba). */ 525 this()(bool[] ba) in(length == ba.length) { 526 foreach (immutable i, const b; ba) 527 this[i] = b; 528 } 529 530 /** Set `this` to the contents of $(D ba). */ 531 this()(const ref bool[capacity] ba) { 532 foreach (immutable i, const b; ba) 533 this[i] = b; 534 } 535 536 bool opCast(T : bool)() const => !this.allZero; 537 538 /// construct from dynamic array 539 @safe nothrow @nogc unittest 540 { 541 static bool[] ba = [1,0,1,0,1]; 542 auto a = StaticBitArray!5(ba); 543 assert(a); 544 assert(!a.allZero); 545 } 546 /// ditto 547 @safe nothrow @nogc unittest 548 { 549 static bool[] ba = [0,0,0]; 550 auto a = StaticBitArray!3(ba); 551 assert(!a); 552 assert(a.allZero); 553 } 554 /// construct from static array 555 @safe nothrow @nogc unittest 556 { 557 static bool[3] ba = [0,0,0]; 558 auto a = StaticBitArray!3(ba); 559 assert(!a); 560 assert(a.allZero); 561 } 562 563 static if (capacity >= 1) { 564 /** Lazy range of the indices of set bits. 565 566 Similar to: `std.bitmanip.bitsSet` 567 568 See_Also: https://dlang.org/phobos/std_bitmanip.html#bitsSet 569 */ 570 struct OneIndexes(Store) { 571 /+ TODO: if (is(Store == StaticBitArray!(_), _)) +/ 572 pure nothrow @safe @nogc: 573 574 this(Store* store) { 575 this._store = store; 576 577 // pre-adjust front index. TODO: make lazy and move to front 578 while (_i < length && !(*_store)[_i]) 579 ++_i; 580 581 // pre-adjust back index. TODO: make lazy and move to front 582 while (_j > 1 && !(*_store)[_j]) 583 --_j; 584 } 585 586 import std.traits : isMutable; 587 static if (isMutable!(typeof(_store))) 588 this(this) @disable; // Rust-style mutable reference semantics 589 590 pragma(inline, true): 591 592 bool empty() const @property => _i > _j; 593 Mod!capacity front() const @property in(!empty) => typeof(return)(_i); /+ TODO: use enforce when it's @nogc +/ 594 Mod!capacity back() const @property in(!empty) => typeof(return)(_j); /+ TODO: use enforce when it's @nogc +/ 595 void popFront() in(!empty) { 596 version (DigitalMars) pragma(inline); 597 while (++_i <= _j) 598 if ((*_store)[_i]) 599 break; 600 } 601 void popBack() in(!empty) { 602 while (_i <= --_j) 603 if ((*_store)[_j]) 604 break; 605 } 606 607 private: 608 Store* _store; // copy of store 609 int _i = 0; // front index into `_store` 610 int _j = (*_store).length - 1; // back index into `_store` 611 } 612 613 /** Returns: a lazy range of the indices of set bits. 614 */ 615 auto oneIndexes()() const => OneIndexes!(typeof(this))(&this); 616 /// ditto 617 alias bitsSet = oneIndexes; 618 619 /** Find index of first cleared (zero) bit or `typeof(return).max` if no bit set. 620 * 621 * Optimized for zeros-sparsity. 622 */ 623 size_t indexOfFirstZero()() const 624 { 625 import nxt.bitarray_algorithm; 626 enum bool blockAlignedLength = capacity % (8*Block.sizeof) == 0; 627 return indexOfFirstZero!(const(Block)[blockCount], 628 blockAlignedLength)(_blocks, length); 629 } 630 631 /** Find index of first set (one) bit or `typeof(return).max` if no bit set. 632 * 633 * Optimized for ones-sparsity. 634 */ 635 size_t indexOfFirstOne()() const 636 { 637 import nxt.bitarray_algorithm : indexOfFirstOne; 638 enum bool blockAlignedLength = capacity % (8*Block.sizeof) == 0; 639 return indexOfFirstOne!(const(Block)[blockCount], 640 blockAlignedLength)(_blocks, length); 641 } 642 643 /** Get number of bits set. */ 644 Mod!(capacity + 1) countOnes()() const /* tlm. TODO: unite with other definitions */ 645 { 646 import nxt.bitarray_algorithm; 647 enum bool blockAlignedLength = capacity % (8*Block.sizeof) == 0; 648 return typeof(return)(nxt.bitarray_algorithm.countOnes!(const(Block)[blockCount], 649 blockAlignedLength)(_blocks, length)); 650 } 651 652 /** Get number of (zero) bits unset. */ 653 size_t countZeros()() const /*tlm*/ 654 { 655 return length - countOnes; 656 } 657 658 /** Get number of bits set divided by length. */ 659 version (none) 660 auto denseness()(int depth = -1) const /*tlm*/ 661 { 662 import nxt.rational : Rational; 663 alias Q = Rational!ulong; 664 return Q(countOnes, length); 665 } 666 667 /** Get number of bits unset divided by length. */ 668 version (none) 669 auto sparseness()(int depth = -1) const /*tlm*/ 670 { 671 import nxt.rational : Rational; 672 alias Q = Rational!ulong; 673 return 1 - denseness(depth); 674 } 675 676 /** Check if `this` has only zeros (is empty). */ 677 bool allZero()() const pure nothrow @safe @nogc 678 { 679 foreach (const block; _fullBlocks) 680 if (block != Block.min) 681 return false; 682 static if (blockCount) 683 if (_restBlock != Block.min) 684 return false; 685 return true; 686 } 687 688 /** Check if `this` has only ones. */ 689 bool allOne()() const 690 { 691 const restBitCount = capacity % bitsPerBlock; 692 const hasRest = restBitCount != 0; 693 if (_blocks.length >= 1) 694 foreach (const block; _blocks[0 .. $ - hasRest]) 695 if (block != Block.max) { return false; } 696 if (restBitCount) 697 return _blocks[$ - 1] == 2^^restBitCount - 1; 698 else 699 return true; 700 } 701 702 /** Find index (starting at `currIx`) of first bit that equals `value`. 703 * 704 * Returns: `true` if index was found (hit index is put into `nextIx`), `false` otherwise. 705 * 706 * TODO: block-optimize for large BitSets 707 */ 708 bool canFindIndexOf(ModUInt)(bool value, 709 Mod!(capacity, ModUInt) currIx, 710 out Mod!(capacity, ModUInt) nextIx) const 711 if (isUnsigned!ModUInt) { 712 if (currIx >= length) { return false; } 713 bool hit = false; 714 foreach (immutable ix_; cast(uint)currIx .. cast(uint)length) { 715 const bool bit = this[ix_]; 716 if (bit == value) { 717 nextIx = typeof(nextIx)(ix_); 718 hit = true; 719 break; 720 } 721 } 722 return hit; 723 } 724 725 bool canFindIndexOf(UInt)(bool value, 726 UInt currIx, 727 out UInt nextIx) const 728 if (isUnsigned!UInt) { 729 if (currIx >= length) { return false; } 730 bool hit = false; 731 foreach (immutable ix_; cast(uint)currIx .. cast(uint)length) { 732 const bool bit = this[ix_]; 733 if (bit == value) { 734 nextIx = typeof(nextIx)(ix_); 735 hit = true; 736 break; 737 } 738 } 739 return hit; 740 } 741 742 } 743 744 /** 745 * Map the $(D StaticBitArray) onto $(D v), with $(D numbits) being the number of bits 746 * in the array. Does not copy the data. 747 * 748 * This is the inverse of $(D opCast). 749 */ 750 /* void init(void[] v, size_t numbits) in { */ 751 /* assert(numbits <= v.length * 8); */ 752 /* assert((v.length & 3) == 0); // must be whole bytes */ 753 /* } do { */ 754 /* _blocks[] = cast(in size_t*)v.ptr[0..v.length]; */ 755 /* } */ 756 757 /** Convert to $(D void[]). */ 758 void[] opCast(T : void[])() @trusted => cast(void[])ptr[0 .. dim]; 759 760 /** Convert to $(D size_t[]). */ 761 size_t[] opCast(T : size_t[])() => ptr[0 .. dim]; 762 /// 763 nothrow unittest 764 { 765 static bool[] ba = [1,0,1,0,1]; 766 auto a = StaticBitArray!5(ba); 767 void[] v = cast(void[])a; 768 assert(v.length == a.dim * size_t.sizeof); 769 } 770 771 /** Complement operator. */ 772 typeof(this) opCom() const @trusted 773 { 774 StaticBitArray result; 775 foreach (const i; 0 .. dim) 776 result.ptr[i] = cast(Block)~cast(ulong)this.ptr[i]; 777 immutable rem = capacity & (bitsPerBlock-1); // number of rest bits in last block 778 if (rem < bitsPerBlock) // rest bits in last block 779 // make remaining bits zero in last block 780 result.ptr[dim - 1] &= ~(~(cast(Block)0) << rem); 781 return result; 782 } 783 784 /** Support for binary operator & for $(D StaticBitArray). */ 785 typeof(this) opBinary(string op)(in typeof(this) e2) const 786 if (op == "&") { 787 StaticBitArray result; 788 result._blocks[] = this._blocks[] & e2._blocks[]; 789 return result; 790 } 791 /// 792 nothrow unittest 793 { 794 const a = StaticBitArray!5([1,0,1,0,1]); 795 auto b = StaticBitArray!5([1,0,1,1,0]); 796 const c = a & b; 797 auto d = StaticBitArray!5([1,0,1,0,0]); 798 assert(c == d); 799 } 800 801 /** Support for binary operator | for $(D StaticBitArray). */ 802 typeof(this) opBinary(string op)(in typeof(this) e2) const if (op == "|") { 803 StaticBitArray result; 804 result._blocks[] = this._blocks[] | e2._blocks[]; 805 return result; 806 } 807 /// 808 nothrow unittest 809 { 810 const a = StaticBitArray!5([1,0,1,0,1]); 811 auto b = StaticBitArray!5([1,0,1,1,0]); 812 const c = a | b; 813 auto d = StaticBitArray!5([1,0,1,1,1]); 814 assert(c == d); 815 } 816 817 /** Support for binary operator ^ for $(D StaticBitArray). */ 818 typeof(this) opBinary(string op)(in typeof(this) e2) const if (op == "^") { 819 StaticBitArray result; 820 result._blocks[] = this._blocks[] ^ e2._blocks[]; 821 return result; 822 } 823 /// 824 nothrow unittest 825 { 826 const a = StaticBitArray!5([1,0,1,0,1]); 827 auto b = StaticBitArray!5([1,0,1,1,0]); 828 const c = a ^ b; 829 auto d = StaticBitArray!5([0,0,0,1,1]); 830 assert(c == d); 831 } 832 833 /** Support for binary operator - for $(D StaticBitArray). 834 * 835 * $(D a - b) for $(D StaticBitArray) means the same thing as $(D a & ~b). 836 */ 837 typeof(this) opBinary(string op)(in typeof(this) e2) const if (op == "-") { 838 StaticBitArray result; 839 result._blocks[] = this._blocks[] & ~e2._blocks[]; 840 return result; 841 } 842 /// 843 nothrow unittest 844 { 845 const a = StaticBitArray!5([1,0,1,0,1]); 846 auto b = StaticBitArray!5([1,0,1,1,0]); 847 const c = a - b; 848 auto d = StaticBitArray!5([0,0,0,0,1]); 849 assert(c == d); 850 } 851 852 /** Support for operator &= for $(D StaticBitArray). 853 */ 854 typeof(this) opOpAssign(string op)(in typeof(this) e2) if (op == "&") { 855 _blocks[] &= e2._blocks[]; 856 return this; 857 } 858 /// 859 nothrow unittest 860 { 861 auto a = StaticBitArray!5([1,0,1,0,1]); 862 const b = StaticBitArray!5([1,0,1,1,0]); 863 a &= b; 864 const c = StaticBitArray!5([1,0,1,0,0]); 865 assert(a == c); 866 } 867 868 /** Support for operator |= for $(D StaticBitArray). 869 */ 870 typeof(this) opOpAssign(string op)(in typeof(this) e2) if (op == "|") { 871 _blocks[] |= e2._blocks[]; 872 return this; 873 } 874 /// 875 nothrow unittest 876 { 877 auto a = StaticBitArray!5([1,0,1,0,1]); 878 const b = StaticBitArray!5([1,0,1,1,0]); 879 a |= b; 880 const c = StaticBitArray!5([1,0,1,1,1]); 881 assert(a == c); 882 } 883 884 /** Support for operator ^= for $(D StaticBitArray). 885 */ 886 typeof(this) opOpAssign(string op)(in typeof(this) e2) if (op == "^") { 887 _blocks[] ^= e2._blocks[]; 888 return this; 889 } 890 /// 891 nothrow unittest 892 { 893 auto a = StaticBitArray!5([1,0,1,0,1]); 894 const b = StaticBitArray!5([1,0,1,1,0]); 895 a ^= b; 896 const c = StaticBitArray!5([0,0,0,1,1]); 897 assert(a == c); 898 } 899 900 /** Support for operator -= for $(D StaticBitArray). 901 * 902 * $(D a -= b) for $(D StaticBitArray) means the same thing as $(D a &= ~b). 903 */ 904 typeof(this) opOpAssign(string op)(in typeof(this) e2) if (op == "-") { 905 _blocks[] &= ~e2._blocks[]; 906 return this; 907 } 908 /// 909 nothrow unittest 910 { 911 auto a = StaticBitArray!5([1,0,1,0,1]); 912 const b = StaticBitArray!5([1,0,1,1,0]); 913 a -= b; 914 const c = StaticBitArray!5([0,0,0,0,1]); 915 assert(a == c); 916 } 917 918 /** Return a string representation of this StaticBitArray. 919 * 920 * Two format specifiers are supported: 921 * $(LI $(B %s) which prints the bits as an array, and) 922 * $(LI $(B %b) which prints the bits as 8-bit byte packets) 923 * separated with an underscore. 924 */ 925 void toString(Sink)(ref scope Sink sink, FormatSpec!char fmt) const @trusted 926 { 927 switch(fmt.spec) { 928 case 'b': 929 return formatBitString(sink); 930 case 's': 931 return formatBitSet(sink); 932 default: 933 throw new Exception("Unknown format specifier: %" ~ fmt.spec); 934 } 935 } 936 /// 937 unittest 938 { 939 const b = StaticBitArray!16(([0, 0, 0, 0, 1, 1, 1, 1, 940 0, 0, 0, 0, 1, 1, 1, 1])); 941 const s1 = format("%s", b); 942 version (none) assert(s1 == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]"); /+ TODO: enable +/ 943 944 version (none) const s2 = format("%b", b); /+ TODO: enable +/ 945 version (none) assert(s2 == "00001111_00001111"); /+ TODO: enable +/ 946 } 947 948 private void formatBitString(Sink)(ref scope Sink sink) const @trusted 949 { 950 import std.range.primitives : put; 951 952 static if (length) { 953 const leftover = capacity % 8; 954 foreach (immutable ix; 0 .. leftover) { 955 const bit = this[ix]; 956 const char[1] res = cast(char)(bit + '0'); 957 sink.put(res[]); 958 } 959 960 if (leftover && 961 capacity > 8) 962 sink.put("_"); // separator 963 964 size_t cnt; 965 foreach (immutable ix; leftover .. capacity) { 966 const bit = this[ix]; 967 const char[1] res = cast(char)(bit + '0'); 968 sink.put(res[]); 969 if (++cnt == 8 && ix != capacity - 1) { 970 sink.put("_"); // separator 971 cnt = 0; 972 } 973 } 974 } 975 } 976 977 private void formatBitSet(Sink)(ref scope Sink sink) const @trusted 978 { 979 sink("["); 980 foreach (immutable ix; 0 .. capacity) { 981 const bit = this[ix]; 982 const char[1] res = cast(char)(bit + '0'); 983 sink(res[]); 984 if (ix+1 < capacity) { sink(", "); } // separator 985 } 986 sink("]"); 987 } 988 989 private: 990 pragma(inline, true) 991 inout(Block)[] _fullBlocks() inout @trusted => _blocks.ptr[0 .. (length / bitsPerBlock)]; 992 993 static if (blockCount) { 994 Block _restBlock() const @trusted 995 { 996 const restBitCount = length % bitsPerBlock; 997 return _blocks[blockCount-1] & ((1UL << restBitCount) - 1); 998 } 999 } 1000 } 1001 1002 /// run-time 1003 pure nothrow @safe @nogc unittest { 1004 import nxt.algorithm.comparison : equal; 1005 1006 enum m = 256; 1007 1008 StaticBitArray!m b0; 1009 1010 import nxt.modulo : Mod; 1011 static assert(is(typeof(b0.oneIndexes.front()) == Mod!m)); 1012 1013 b0[1] = 1; 1014 b0[2] = 1; 1015 1016 b0[m/2 - 11] = 1; 1017 b0[m/2 - 1] = 1; 1018 b0[m/2] = 1; 1019 b0[m/2 + 1] = 1; 1020 b0[m/2 + 11] = 1; 1021 1022 b0[m - 3] = 1; 1023 b0[m - 2] = 1; 1024 1025 assert(b0.oneIndexes.equal([1, 2, 1026 m/2 - 11, m/2 - 1, m/2, m/2 + 1, m/2 + 11, 1027 m - 3, 1028 m - 2].s[])); 1029 assert(b0.countOnes == 9); 1030 } 1031 1032 /// run-time 1033 pure nothrow @safe @nogc unittest { 1034 import nxt.algorithm.comparison : equal; 1035 1036 enum m = 256; 1037 1038 StaticBitArray!m b0; 1039 1040 import nxt.modulo : Mod; 1041 static assert(is(typeof(b0.oneIndexes.front()) == Mod!m)); 1042 1043 b0[0] = 1; 1044 b0[1] = 1; 1045 b0[m/2 - 11] = 1; 1046 b0[m/2 - 1] = 1; 1047 b0[m/2] = 1; 1048 b0[m/2 + 1] = 1; 1049 b0[m/2 + 11] = 1; 1050 b0[m - 2] = 1; 1051 b0[m - 1] = 1; 1052 1053 assert(b0.oneIndexes.equal([0, 1, 1054 m/2 - 11, m/2 - 1, m/2, m/2 + 1, m/2 + 11, 1055 m - 2, 1056 m - 1].s[])); 1057 assert(b0.countOnes == 9); 1058 } 1059 1060 /// ditto 1061 pure nothrow @safe @nogc unittest { 1062 import std.traits : isIterable; 1063 static assert(isIterable!(StaticBitArray!256)); 1064 } 1065 1066 /// test ubyte access 1067 pure nothrow @safe @nogc unittest { 1068 auto b8 = StaticBitArray!(8, ubyte)(); 1069 b8[0] = 1; 1070 b8[1] = 1; 1071 b8[3] = 1; 1072 b8[6] = 1; 1073 1074 assert(b8.ubytes == [64 + 8 + 2 + 1].s[]); 1075 1076 alias Ix = b8.Index; 1077 Ix nextIx; 1078 1079 assert(b8.canFindIndexOf(true, Ix(0), nextIx)); 1080 assert(nextIx == 0); 1081 1082 assert(b8.canFindIndexOf(true, Ix(1), nextIx)); 1083 assert(nextIx == 1); 1084 1085 assert(b8.canFindIndexOf(true, Ix(2), nextIx)); 1086 assert(nextIx == 3); 1087 1088 assert(b8.canFindIndexOf(true, Ix(3), nextIx)); 1089 assert(nextIx == 3); 1090 1091 assert(b8.canFindIndexOf(true, Ix(4), nextIx)); 1092 assert(nextIx == 6); 1093 1094 assert(!b8.canFindIndexOf(true, Ix(7), nextIx)); 1095 } 1096 1097 /// test all zero and all one predicates 1098 pure nothrow @safe @nogc unittest { 1099 static void test(size_t restBitCount)() { 1100 enum n = 8*size_t.sizeof + restBitCount; 1101 1102 auto bs = StaticBitArray!(n, size_t)(); 1103 1104 assert(bs.allZero); 1105 assert(!bs.allOne); 1106 1107 foreach (immutable i; 0 .. n - 1) { 1108 bs[i] = true; 1109 assert(!bs.allZero); 1110 assert(!bs.allOne); 1111 } 1112 bs[n - 1] = true; 1113 1114 assert(bs.allOne); 1115 } 1116 test!0; 1117 test!1; 1118 test!2; 1119 test!37; 1120 test!62; 1121 test!63; 1122 } 1123 1124 /// ditto 1125 version (none) /+ TODO: enable +/ 1126 @safe unittest { 1127 import std.format : format; 1128 1129 const b0_ = StaticBitArray!0([]); 1130 const b0 = b0_; 1131 assert(format("%s", b0) == "[]"); 1132 assert(format("%b", b0) is null); 1133 1134 const b1_ = StaticBitArray!1([1]); 1135 const b1 = b1_; 1136 assert(format("%s", b1) == "[1]"); 1137 assert(format("%b", b1) == "1"); 1138 1139 const b4 = StaticBitArray!4([0, 0, 0, 0]); 1140 assert(format("%b", b4) == "0000"); 1141 1142 const b8 = StaticBitArray!8([0, 0, 0, 0, 1, 1, 1, 1]); 1143 assert(format("%s", b8) == "[0, 0, 0, 0, 1, 1, 1, 1]"); 1144 assert(format("%b", b8) == "00001111"); 1145 1146 const b16 = StaticBitArray!16([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); 1147 assert(format("%s", b16) == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]"); 1148 assert(format("%b", b16) == "00001111_00001111"); 1149 1150 const b9 = StaticBitArray!9([1, 0, 0, 0, 0, 1, 1, 1, 1]); 1151 assert(format("%b", b9) == "1_00001111"); 1152 1153 const b17 = StaticBitArray!17([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]); 1154 assert(format("%b", b17) == "1_00001111_00001111"); 1155 } 1156 1157 /// test range 1158 pure nothrow @safe unittest { 1159 static testRange(Block)() { 1160 StaticBitArray!(6, Block) bs = [false, 1, 0, 0, true, 0]; 1161 bs.put(3, true); 1162 1163 import nxt.algorithm.comparison : equal; 1164 1165 assert(bs[0] == false); 1166 assert(bs[1] == true); 1167 assert(bs[2] == false); 1168 assert(bs[3] == true); 1169 assert(bs[4] == true); 1170 assert(bs[5] == false); 1171 1172 assert(bs.at!0 == false); 1173 assert(bs.at!1 == true); 1174 assert(bs.at!2 == false); 1175 assert(bs.at!3 == true); 1176 assert(bs.at!4 == true); 1177 assert(bs.at!5 == false); 1178 1179 // test slicing 1180 assert(bs[].equal([0, 1, 0, 1, 1, 0].s[])); 1181 assert(bs[1 .. 4].equal([1, 0, 1].s[])); 1182 1183 auto rs = bs[1 .. 6 - 1]; /+ TODO: Use opDollar +/ 1184 assert(rs.length == 4); 1185 assert(rs.front == true); 1186 assert(rs.back == true); 1187 1188 rs.popFront(); 1189 assert(rs.front == false); 1190 assert(rs.back == true); 1191 1192 rs.popBack(); 1193 assert(rs.front == false); 1194 assert(rs.back == true); 1195 1196 rs.popFront(); 1197 assert(rs.front == true); 1198 assert(rs.back == true); 1199 1200 rs.popBack(); 1201 assert(rs.length == 0); 1202 assert(rs.empty); 1203 } 1204 1205 import std.meta : AliasSeq; 1206 foreach (Block; AliasSeq!(ubyte, ushort, uint, ulong, size_t)) 1207 testRange!Block; 1208 } 1209 1210 /// 1211 pure nothrow @safe @nogc unittest { 1212 alias Block = size_t; 1213 enum blockCount = 2; 1214 enum n = blockCount * 8*Block.sizeof - 1; 1215 StaticBitArray!(n) x; 1216 static assert(x.blockCount == blockCount); 1217 1218 assert(x.indexOfFirstOne == n); 1219 x[n - 1] = true; 1220 assert(x.indexOfFirstOne == x.length - 1); 1221 x[n - 2] = true; 1222 assert(x.indexOfFirstOne == x.length - 2); 1223 1224 x[n/2 + 1] = true; 1225 assert(x.indexOfFirstOne == x.length/2 + 1); 1226 x[n/2] = true; 1227 assert(x.indexOfFirstOne == x.length/2); 1228 x[n/2 - 1] = true; 1229 assert(x.indexOfFirstOne == x.length/2 - 1); 1230 1231 x[0] = true; 1232 assert(x.indexOfFirstOne == 0); 1233 assert(x[0]); 1234 assert(!x[1]); 1235 1236 x[1] = true; 1237 assert(x[1]); 1238 1239 x[1] = false; 1240 assert(!x[1]); 1241 } 1242 1243 /// Test opSliceAssign. 1244 pure nothrow @safe @nogc unittest { 1245 alias Block = size_t; 1246 enum blockCount = 2; 1247 enum n = blockCount * 8*Block.sizeof - 1; 1248 1249 StaticBitArray!(n) x; 1250 assert(x.countOnes == 0); 1251 1252 x[] = true; 1253 assert(x.countOnes == n); 1254 1255 x[] = false; 1256 assert(x.countOnes == 0); 1257 } 1258 1259 version (unittest) { 1260 import nxt.array_help : s; 1261 }