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