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 }