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 }