1 2 module roaring.roaring; 3 4 import roaring.c; 5 6 Bitmap bitmapOf(const uint[] args ...) 7 { 8 return Bitmap.fromArray(args); 9 } 10 11 Bitmap bitmapOf(const uint[] bits) 12 { 13 return Bitmap.fromArray(bits); 14 } 15 16 Bitmap bitmapOf(T)(T rng) 17 { 18 auto r = new Bitmap; 19 foreach (bit; rng) r.add(bit); 20 return r; 21 } 22 23 unittest 24 { 25 import std.range : iota; 26 import std.conv : to; 27 const r1 = bitmapOf(0, 1, 2, 3, 4); 28 const r2 = bitmapOf([0, 1, 2, 3, 4]); 29 const r3 = bitmapOf(5.iota); 30 assert("{0, 1, 2, 3, 4}" == to!string(r1)); 31 assert((r1 == r2) && (r1 == r3)); 32 } 33 34 Bitmap readBitmap(const char[] buf) 35 { 36 return Bitmap.read(buf); 37 } 38 39 char[] writeBitmap(const Bitmap r) 40 { 41 return r.write(); 42 } 43 44 unittest 45 { 46 import core.exception : OutOfMemoryError; 47 const r1 = bitmapOf(5, 1, 2, 3, 5, 6); 48 char[] buf = writeBitmap(r1); 49 const r2 = readBitmap(buf); 50 assert(r1 == r2); 51 try { 52 buf.length = 0; 53 readBitmap(buf) || assert(false); 54 } 55 catch (OutOfMemoryError e) { 56 // pass 57 } 58 } 59 60 BitRange range(const Bitmap r) 61 { 62 return new BitRange(r.bitmap); 63 } 64 65 class BitRange 66 { 67 @nogc @property @safe 68 bool empty() const pure 69 { 70 return !this.it.has_value; 71 } 72 73 @nogc @property @safe 74 uint front() const pure 75 { 76 return this.it.current_value; 77 } 78 79 @nogc 80 void popFront() 81 { 82 if (it.has_value) roaring_advance_uint32_iterator(this.it); 83 } 84 85 @nogc 86 private this(const roaring_bitmap_t *bmp) 87 { 88 this.it = roaring_create_iterator(bmp); 89 } 90 91 @nogc 92 private ~this() 93 { 94 if (this.it) roaring_free_uint32_iterator(this.it); 95 } 96 97 private roaring_uint32_iterator_t *it; 98 } 99 100 unittest 101 { 102 import std.algorithm : filter, sum; 103 assert(6 == bitmapOf(1, 2, 3, 4, 5).range.filter!(a => a % 2 == 0).sum); 104 } 105 106 107 108 class Bitmap 109 { 110 /// Creates an empty bitmap 111 this() 112 { 113 this(roaring_bitmap_create()); 114 } 115 116 ~this() 117 { 118 roaring_bitmap_free(this.bitmap); 119 this.bitmap = null; 120 } 121 122 /** Creates an empty bitmap with a predefined capacity 123 PARAMS 124 uint cap: The predefined capacity 125 */ 126 this(const uint cap) 127 { 128 this(roaring_bitmap_create_with_capacity(cap)); 129 } 130 131 private this(roaring_bitmap_t *r) 132 { 133 this.bitmap = r; 134 } 135 136 private static Bitmap fromArray(const uint[] bits) 137 { 138 Bitmap r = new Bitmap(cast(uint)bits.length); 139 roaring_bitmap_add_many(r.bitmap, bits.length, bits.ptr); 140 return r; 141 } 142 143 private static Bitmap fromRange(const ulong min, const ulong max, const uint step) 144 { 145 auto rr = roaring_bitmap_from_range(min, max, step); 146 return new Bitmap(rr); 147 } 148 149 private static Bitmap read(const char[] buf) 150 { 151 import core.exception : OutOfMemoryError; 152 auto rr = roaring_bitmap_portable_deserialize_safe(buf.ptr, buf.length); 153 if (!rr) { 154 throw new OutOfMemoryError; 155 } 156 return new Bitmap(rr); 157 } 158 159 private char[] write() const 160 { 161 char[] buf = new char[sizeInBytes]; 162 const size = roaring_bitmap_portable_serialize(this.bitmap, buf.ptr); 163 return buf[0..size]; 164 } 165 166 @nogc @property @safe 167 uint length() const pure 168 { 169 return cast(uint)roaring_bitmap_get_cardinality(this.bitmap); 170 } 171 172 @nogc @property @safe 173 void copyOnWrite(bool enable) pure 174 { 175 this.bitmap.copy_on_write = enable; 176 } 177 178 @nogc @property @safe 179 bool copyOnWrite() const pure 180 { 181 return this.bitmap.copy_on_write; 182 } 183 184 unittest 185 { 186 Bitmap r = new Bitmap; 187 r.copyOnWrite = true; 188 assert(r.copyOnWrite == true); 189 } 190 191 uint[] toArray() const 192 { 193 uint[] a = new uint[length]; 194 roaring_bitmap_to_uint32_array(this.bitmap, a.ptr); 195 return a[]; 196 } 197 198 unittest 199 { 200 const r = bitmapOf(5, 1, 2, 3, 5, 6); 201 uint[] a = r.toArray; 202 assert(a.length == 5); 203 } 204 205 @nogc @safe 206 void add(const uint x) 207 { 208 roaring_bitmap_add(this.bitmap, x); 209 } 210 211 unittest 212 { 213 Bitmap r = new Bitmap; 214 r.add(5); 215 assert(5 in r); 216 } 217 218 /** 219 * Adds all values from `min` (included) to `max` (excluded) 220 */ 221 @nogc @safe 222 void addRange(const ulong min, const ulong max) 223 { 224 roaring_bitmap_add_range(this.bitmap, min, max); 225 } 226 unittest 227 { 228 import std.range : iota; 229 Bitmap r = new Bitmap; 230 r.addRange(0, 10); 231 assert(r == bitmapOf(iota(0, 10))); 232 } 233 234 /** 235 * Remove value x 236 * 237 */ 238 @nogc @safe 239 void remove(const uint x) 240 { 241 roaring_bitmap_remove(this.bitmap, x); 242 } 243 244 unittest 245 { 246 Bitmap r = bitmapOf(5); 247 r.remove(5); 248 assert(5 !in r); 249 } 250 251 /** 252 * Check if value x is present 253 */ 254 @nogc @safe 255 bool contains(const uint x) const pure 256 { 257 return roaring_bitmap_contains(this.bitmap, x); 258 } 259 260 /** 261 * Check if all values from start (included) to end (excluded) are present 262 */ 263 @nogc @safe 264 bool containsRange(const uint start, const uint end) const pure 265 { 266 return roaring_bitmap_contains_range(this.bitmap, start, end); 267 } 268 unittest 269 { 270 const r1 = bitmapOf(1, 2, 3, 4, 6, 7, 8); 271 assert(r1.containsRange(2, 5)); 272 assert(!r1.containsRange(2, 6)); 273 } 274 275 /** 276 * Returns the number of elements present between `start` (included) and `end` (excluded) 277 */ 278 @nogc @safe 279 ulong lengthInRange(const ulong start, const ulong end) const pure 280 { 281 return roaring_bitmap_range_cardinality(this.bitmap, start, end); 282 } 283 unittest 284 { 285 const r1 = bitmapOf(1, 2, 3, 4, 5); 286 assert(r1.lengthInRange(2, 5) == 3); 287 assert(r1.lengthInRange(2, 6) == 4); 288 assert(r1.lengthInRange(2, 7) == 4); 289 } 290 291 /** 292 * Return the largest value (if not empty) 293 * 294 */ 295 @nogc @property @safe 296 uint maximum() const pure 297 { 298 return roaring_bitmap_maximum(this.bitmap); 299 } 300 301 /** 302 * Return the smallest value (if not empty) 303 * 304 */ 305 @nogc @property @safe 306 uint minimum() const pure 307 { 308 return roaring_bitmap_minimum(this.bitmap); 309 } 310 311 unittest 312 { 313 const r2 = bitmapOf(100, 10, uint.max); 314 assert(r2.minimum == 10); 315 assert(r2.maximum == uint.max); 316 } 317 318 @nogc @property @safe 319 size_t sizeInBytes() const pure 320 { 321 return roaring_bitmap_portable_size_in_bytes(this.bitmap); 322 } 323 324 unittest 325 { 326 assert(bitmapOf(5).sizeInBytes > 4); 327 } 328 329 /** 330 * Returns the number of integers that are smaller or equal to x. 331 */ 332 @nogc @safe 333 ulong rank(const uint rank) const pure 334 { 335 return roaring_bitmap_rank(this.bitmap, rank); 336 } 337 338 unittest 339 { 340 const r = bitmapOf(5, 1, 2, 3, 5, 6); 341 assert(r.rank(4) == 3); 342 } 343 344 @nogc @safe 345 bool optimize() 346 { 347 return roaring_bitmap_run_optimize(this.bitmap); 348 } 349 350 unittest 351 { 352 auto r1 = bitmapOf(5, 1, 2, 3, 5, 6); 353 r1.copyOnWrite = true; 354 //check optimization 355 auto size1 = r1.sizeInBytes; 356 r1.optimize; 357 assert(r1.sizeInBytes < size1); 358 } 359 360 int opApply(int delegate(ref uint value) dg) const 361 { 362 const bmp = this.bitmap; 363 auto it = roaring_create_iterator(bmp); 364 int dgReturn = 0; 365 while (it.has_value) { 366 dgReturn = dg(it.current_value); 367 if (dgReturn) break; 368 roaring_advance_uint32_iterator(it); 369 } 370 roaring_free_uint32_iterator(it); 371 return dgReturn; 372 } 373 374 unittest 375 { 376 const bitmap = bitmapOf(5, 1, 2, 3, 5, 6); 377 ulong sum; 378 foreach (bit; bitmap) { 379 sum += bit; 380 } 381 assert(sum == 17); 382 } 383 384 // TODO: un-duplicate this method with ^^^ 385 int opApply(int delegate(ref uint index, ref uint value) dg) const 386 { 387 const bmp = this.bitmap; 388 auto it = roaring_create_iterator(bmp); 389 int dgReturn = 0; 390 uint index = 0; 391 while (it.has_value) { 392 dgReturn = dg(index, it.current_value); 393 if (dgReturn) break; 394 index += 1; 395 roaring_advance_uint32_iterator(it); 396 } 397 roaring_free_uint32_iterator(it); 398 return dgReturn; 399 } 400 401 unittest 402 { 403 const bitmap = bitmapOf(5, 1, 2, 3, 5, 6); 404 ulong bitSum; 405 int iSum; 406 foreach (i, bit; bitmap) { 407 bitSum += bit; 408 iSum += i; 409 } 410 assert(bitSum == 17); 411 assert(iSum == 10); 412 } 413 414 Bitmap opBinary(const string op)(const Bitmap b) const 415 if (op == "&" || op == "|" || op == "^") 416 { 417 roaring_bitmap_t *result; 418 static if (op == "&") result = roaring_bitmap_and(this.bitmap, b.bitmap); 419 else static if (op == "|") result = roaring_bitmap_or(this.bitmap, b.bitmap); 420 else static if (op == "^") result = roaring_bitmap_xor(this.bitmap, b.bitmap); 421 else static assert(0, "Operator " ~ op ~ " not implemented."); 422 423 return new Bitmap(result); 424 } 425 426 unittest 427 { 428 const r1 = bitmapOf(5, 1, 2, 3, 5, 6); 429 const r2 = bitmapOf(6, 7, 8); 430 assert((r1 | r2) == bitmapOf(1, 2, 3, 5, 6, 7, 8)); 431 assert((r1 & r2) == bitmapOf(6)); 432 assert((r1 ^ r2) == bitmapOf(1, 2, 3, 5, 7, 8)); 433 } 434 435 @nogc 436 void opOpAssign(const string op)(const Bitmap b) 437 if (op == "&" || op == "|" || op == "^") 438 { 439 static if (op == "&") roaring_bitmap_and_inplace(this.bitmap, b.bitmap); 440 else static if (op == "|") roaring_bitmap_or_inplace(this.bitmap, b.bitmap); 441 else static if (op == "^") roaring_bitmap_xor_inplace(this.bitmap, b.bitmap); 442 else static assert(0, "Operator " ~ op ~ " not implemented."); 443 } 444 445 unittest 446 { 447 auto r1 = bitmapOf(5, 1, 2, 3, 5, 6); 448 const r2 = bitmapOf(6, 7, 8); 449 r1 |= r2; 450 assert(r1 == bitmapOf(1, 2, 3, 5, 6, 7, 8)); 451 452 r1 = bitmapOf(5, 1, 2, 3, 5, 6); 453 r1 &= r2; 454 assert(r1 == bitmapOf(6)); 455 456 r1 = bitmapOf(5, 1, 2, 3, 5, 6); 457 r1 ^= r2; 458 assert(r1 == bitmapOf(1, 2, 3, 5, 7, 8)); 459 } 460 461 /** 462 * Computes the difference of this bitmap and `b`; that is returns the 463 * elements of `this` that are not in `b`. 464 */ 465 Bitmap andNot(const Bitmap b) const 466 { 467 return new Bitmap(roaring_bitmap_andnot(bitmap, b.bitmap)); 468 } 469 unittest 470 { 471 const a = bitmapOf(1, 2, 4, 5); 472 const b = bitmapOf(2, 3, 5); 473 assert(a.andNot(b) == bitmapOf(1, 4)); 474 assert(b.andNot(a) == bitmapOf(3)); 475 } 476 477 /** 478 * Performs the andNot operation in place. 479 */ 480 void andNotInPlace(const Bitmap b) 481 { 482 roaring_bitmap_andnot_inplace(bitmap, b.bitmap); 483 } 484 unittest 485 { 486 auto a = bitmapOf(1, 2, 4, 5); 487 const b = bitmapOf(2, 3, 5); 488 a.andNotInPlace(b); 489 assert(a == bitmapOf(1, 4)); 490 } 491 492 @nogc @safe 493 bool opBinaryRight(const string op)(const uint x) const 494 if (op == "in") 495 { 496 static if (op == "in") return roaring_bitmap_contains(this.bitmap, x); 497 else static assert(0, "Operator " ~ op ~ " not implemented."); 498 } 499 500 unittest 501 { 502 import std.range : iota; 503 Bitmap r1 = bitmapOf(iota(100, 1000)); 504 assert(500 in r1); 505 } 506 507 bool opBinaryRight(const string op)(const Bitmap b) const 508 if (op == "in") 509 { 510 static if (op == "in") return roaring_bitmap_is_subset(b.bitmap, this.bitmap); 511 else static assert(0, "Operator " ~ op ~ " not implemented."); 512 } 513 514 unittest 515 { 516 import std.range : iota; 517 Bitmap r1 = bitmapOf(iota(100, 1000)); 518 Bitmap r2 = bitmapOf(iota(200, 400)); 519 assert(r2 in r1); 520 } 521 522 uint opIndex(uint rank) const 523 { 524 import core.exception : RangeError; 525 uint v; 526 if (!roaring_bitmap_select(this.bitmap, rank, &v)) { 527 throw new RangeError; 528 } 529 return v; 530 } 531 532 unittest 533 { 534 import core.exception : RangeError; 535 const bitmap = bitmapOf(5, 1, 2, 3, 5, 6); 536 assert(5 == bitmap[3]); 537 // accessing an index > cardinality(set) throws a RangeError 538 try { 539 bitmap[999] || assert(false); 540 } 541 catch (RangeError e) { 542 // pass 543 } 544 } 545 546 Bitmap opSlice(int start, int end) const 547 { 548 import core.exception : RangeError; 549 if (start < 0 || start >= opDollar) { 550 throw new RangeError; 551 } 552 Bitmap r = new Bitmap; 553 foreach (i, bit; this) { 554 if (i < start) continue; 555 if (i >= end) break; 556 r.add(this[i]); 557 } 558 return r; 559 } 560 561 @nogc @property @safe 562 uint opDollar() const pure 563 { 564 return length; 565 } 566 567 unittest 568 { 569 import core.exception : RangeError; 570 const bitmap = bitmapOf(5, 1, 2, 3, 5, 6); 571 assert(bitmapOf(3, 5, 6) == bitmap[2..$]); 572 573 import core.exception : RangeError; 574 // accessing an index < 0 throws a RangeError 575 try { 576 bitmap[-1..$] || assert(false); 577 } 578 catch (RangeError e) { 579 // pass 580 } 581 582 import core.exception : RangeError; 583 // accessing an index > $ throws a RangeError 584 try { 585 bitmap[$..$] || assert(false); 586 } 587 catch (RangeError e) { 588 // pass 589 } 590 } 591 592 override bool opEquals(Object b) const 593 { 594 import std.stdio : writeln; 595 if (this is b) return true; 596 if (b is null) return false; 597 if (typeid(this) == typeid(b)) { 598 return roaring_bitmap_equals(this.bitmap, (cast(Bitmap)b).bitmap); 599 } 600 return false; 601 } 602 603 unittest 604 { 605 const r1 = Bitmap.fromRange(10, 20, 3); 606 const r2 = Bitmap.fromArray([10, 13, 16, 19]); 607 assert(r1 == r2); 608 609 const r3 = Bitmap.fromArray([10]); 610 assert(r1 != r3); 611 612 assert(r1 != new Object); 613 } 614 615 void opOpAssign(const string op)(const uint x) 616 if (op == "|" || op == "-") 617 { 618 static if (op == "|") roaring_bitmap_add(this.bitmap, x); 619 else static if (op == "-") roaring_bitmap_remove(this.bitmap, x); 620 } 621 622 unittest 623 { 624 Bitmap r1 = new Bitmap; 625 r1 |= 500; 626 assert(500 in r1); 627 } 628 629 void toString(scope void delegate(const(char)[]) sink) const 630 { 631 import std.format : format; 632 import std.range : enumerate; 633 sink("{"); 634 foreach (i, bit; this) { 635 if (i == 0) sink(format("%d", bit)); 636 else sink(format(", %d", bit)); 637 } 638 sink("}"); 639 } 640 641 unittest 642 { 643 import std.conv : to; 644 const bitmap = bitmapOf(5, 1, 2, 3, 5, 6); 645 assert("{1, 2, 3, 5, 6}" == to!string(bitmap)); 646 } 647 648 Bitmap dup() const @property 649 { 650 return new Bitmap(roaring_bitmap_copy(bitmap)); 651 } 652 653 unittest 654 { 655 const original = bitmapOf(1, 2, 3, 4); 656 auto copy = original.dup; 657 658 // Copy is editable whereas original is const 659 copy.add(5); 660 assert(5 !in original); 661 assert(5 in copy); 662 } 663 664 private roaring_bitmap_t* bitmap; 665 } 666 667 unittest 668 { 669 import std.stdio : writefln, writeln; 670 import roaring.roaring : Bitmap, bitmapOf; 671 672 // create a new roaring bitmap instance 673 auto r1 = new Bitmap; 674 675 // add some bits to the bitmap 676 r1.add(5); 677 678 // create from an array 679 auto ra = bitmapOf([1, 2, 3]); 680 681 // create from a range 682 import std.range : iota; 683 assert(bitmapOf(0, 1, 2, 3) == bitmapOf(4.iota)); 684 685 // create a new roaring bitmap instance from some numbers 686 auto r2 = bitmapOf(1, 3, 5, 15); 687 688 // check whether a value is contained 689 assert(r2.contains(5)); 690 assert(5 in r2); // r2 contains 5 691 assert(99 !in r2); // r2 does not contain 99 692 693 // get minimum and maximum values in a bitmap 694 assert(r2.minimum == 1); 695 assert(r2.maximum == 15); 696 697 // remove a value from the bitmap 698 r2.remove(5); 699 assert(!r2.contains(5)); 700 701 // compute how many bits there are: 702 assert(3 == r2.length); 703 704 // check whether a bitmap is subset of another 705 const sub = bitmapOf(1, 5); 706 const sup = bitmapOf(1, 2, 3, 4, 5, 6); 707 assert(sub in sup); 708 709 // iterate on a bitmap 710 const r3 = bitmapOf(1, 5, 10, 20); 711 ulong s = 0; 712 foreach (bit; r3) { 713 s += bit; 714 } 715 assert(s == 36); 716 717 // iterate on a bitmap and index 718 foreach(i, bit; r3) { 719 writefln("%d: %d", i, bit); 720 } 721 722 import roaring.roaring : readBitmap, writeBitmap; 723 // serialize the bitmap 724 char[] buf = writeBitmap(r3); 725 // deserialize from a char array 726 const r3Copy = readBitmap(buf); 727 assert(r3 == r3Copy); 728 729 // find the intersection of bitmaps 730 const r4 = bitmapOf(1, 5, 6); 731 const r5 = bitmapOf(1, 2, 3, 4, 5); 732 assert((r4 & r5) == bitmapOf(1, 5)); 733 // find the union of bitmaps 734 assert((r4 | r5) == bitmapOf(1, 2, 3, 4, 5, 6)); 735 736 const r6 = bitmapOf(0, 10, 20, 30, 40, 50); 737 // get the bit for the index 738 assert(20 == r6[2]); 739 // slice a bitmap 740 assert(bitmapOf(30, 40, 50) == r6[3..$]); 741 742 // convert the bitmap to a string 743 writeln("Bitmap: ", r6); 744 import std.conv : to; 745 assert("{0, 10, 20, 30, 40, 50}" == to!string(r6)); 746 747 // the bit range! 748 import std.algorithm : filter, sum; 749 import roaring.roaring : range, BitRange; 750 // sum of bits in r6 which are bit % 20==0 751 assert(60 == r6.range.filter!(b => b % 20 == 0).sum); 752 753 // if your bitmaps have long runs, you can compress them 754 auto r7 = bitmapOf(1000.iota); 755 writeln("size before optimize = ", r7.sizeInBytes); 756 r7.optimize(); 757 writeln("size after optimize = ", r7.sizeInBytes); 758 759 // copy a bitmap (uses copy-on-write under the hood) 760 const r8 = r7.dup; 761 assert(r8 == r7); 762 }