1 /** Algorithms for dynamic and static bitarrays based with integral blocks.
2  *
3  * Algorithms are qualified as `package` because they should only be used by
4  * bit-array containers.
5  */
6 module nxt.bitarray_algorithm;
7 
8 @safe pure nothrow @nogc:
9 
10 size_t countOnes(Blocks, bool blockAlignedLength = false)(const scope auto ref Blocks blocks, size_t length) @trusted
11 if (isBlocks!Blocks)
12 {
13     alias Block = typeof(Blocks.init[0]);
14     enum bitsPerBlock = 8*Block.sizeof;
15 
16     typeof(return) result = 0;
17 
18     import core.bitop : popcnt;
19     const fullBlockCount = length / bitsPerBlock;
20     foreach (const block; blocks.ptr[0 .. fullBlockCount])
21     {
22         result += cast(typeof(result))popcnt(block);
23     }
24 
25     // dbg("bitsPerBlock:", bitsPerBlock,
26     //     " length:", length,
27     //     " fullBlockCount:", fullBlockCount);,
28 
29     static if (!blockAlignedLength)
30     {
31         const restBitCount = length % bitsPerBlock;
32         if (restBitCount)
33         {
34             result += cast(typeof(result))popcnt(blocks.ptr[fullBlockCount] & ((1UL << restBitCount)-1));
35         }
36 
37         // dbg(" restBitCount:", restBitCount);
38     }
39 
40     return typeof(return)(result);
41 }
42 
43 /// Test `countOnes` with full blocks.
44 @safe pure nothrow @nogc unittest
45 {
46     alias Block = size_t;
47     enum bitsPerBlock = 8*Block.sizeof;
48 
49     enum blockCount = 3;
50     Block[blockCount] x;
51     const length = 8*x.sizeof;
52     assert(countOnes(x, length) == 0);
53 
54     x[0] = 1;
55     assert(countOnes(x, length) == 1);
56 
57     x[0] = 1+2;
58     assert(countOnes(x, length) == 2);
59 
60     x[0] = 1+2;
61     x[1] = 1+2;
62     assert(countOnes(x, length) == 4);
63 
64     x[0] = 1+2;
65     x[1] = 1+2;
66     x[2] = 1+2;
67     assert(countOnes(x, length) == 6);
68 }
69 
70 /// Test `countOnes` with partial blocks.
71 @safe pure nothrow @nogc unittest
72 {
73     alias Block = size_t;
74     static void test(uint blockCount)()
75     {
76         Block[blockCount] x;
77         foreach (const length; 1 .. 8*x.sizeof)
78         {
79             assert(countOnes(x, length) == 0);
80 
81             x[0] |= 1UL << (length-1);
82             assert(countOnes(x, length) == 1);
83             x[0] = 0;
84         }
85     }
86     test!1();
87     test!2();
88     test!3();
89 }
90 
91 /** Find index of first cleared (zero) bit or `length` if no bit cleared.
92  *
93  * Optimized for zeros-sparsity.
94  */
95 size_t indexOfFirstZero(Blocks, bool blockAlignedLength = false)(const scope auto ref Blocks blocks, size_t length)
96 if (isBlocks!Blocks)
97 {
98     static if (blockAlignedLength)
99     {
100         foreach (const blockIndex, block; blocks)
101         {
102             if (block != block.max) // optimize for zeros-sparsity
103             {
104                 import core.bitop : bsf;
105                 const hitIndex = blockIndex*8*block.sizeof + bsf(~block); // TODO: is there a builtin for `bsf(~block)`?
106                 return hitIndex < length ? hitIndex : length; // if hit beyond end miss
107             }
108         }
109     }
110     else
111     {
112         // TODO: handle blocks with garbage in the rest block
113         foreach (const blockIndex, block; blocks)
114         {
115             if (block != block.max) // optimize for zeros-sparsity
116             {
117                 import core.bitop : bsf;
118                 const hitIndex = blockIndex*8*block.sizeof + bsf(~block); // TODO: is there a builtin for `bsf(~block)`?
119                 return hitIndex < length ? hitIndex : length; // if hit beyond end miss
120             }
121         }
122     }
123     return length;              // miss
124 }
125 
126 /** Find index of first set (one) bit or `length` if no bit cleared.
127  *
128  * Optimized for ones-sparsity.
129  */
130 size_t indexOfFirstOne(Blocks, bool blockAlignedLength = false)(const scope auto ref Blocks blocks, size_t length)
131 if (isBlocks!Blocks)
132 {
133     static if (blockAlignedLength)
134     {
135         foreach (const blockIndex, const block; blocks)
136         {
137             if (block != block.min) // optimize for ones-sparsity
138             {
139                 import core.bitop : bsf;
140                 const hitIndex = blockIndex*8*block.sizeof + bsf(block);
141                 return hitIndex < length ? hitIndex : length; // if hit beyond end miss
142             }
143         }
144     }
145     else
146     {
147         // TODO: handle blocks with garbage in the rest block
148         foreach (const blockIndex, const block; blocks)
149         {
150             if (block != block.min) // optimize for ones-sparsity
151             {
152                 import core.bitop : bsf;
153                 const hitIndex = blockIndex*8*block.sizeof + bsf(block);
154                 return hitIndex < length ? hitIndex : length; // if hit beyond end miss
155             }
156         }
157     }
158     return length;              // miss
159 }
160 
161 private enum isBlocks(Blocks) = (is(typeof(Blocks.init[0]) : uint) ||
162                                  is(typeof(Blocks.init[0]) : ulong));