1 /**
2 License: public domain
3 Authors: Simon Bürger
4 */
5 
6 module jive.bitarray;
7 
8 private import jive.internal;
9 private import core.bitop;
10 private import std.algorithm : max;
11 
12 /**
13  *  Efficient version of Array!bool using only one bit per entry.
14  *  But note that the interface is not compatible with Array!bool. In
15  *  particular no ranges are provided. This is a design choice because using
16  *  a bit-range in general algorithms is often very inefficient. In contrast
17  *  specialized algorithms working on BitArray are typically very fast.
18  */
19 struct BitArray
20 {
21 	@nogc: nothrow: pure: @safe:
22 
23 	alias limb = size_t;
24 	enum limbBits = limb.sizeof*8;
25 
26 	private limb* _ptr;			// unused bits are always 0
27 	private size_t _capacity;	// allocated limbs (not bits)
28 	private size_t _length;
29 
30 	/** constructor for given length */
31 	this(size_t size)
32 	{
33 		_length = size;
34 		_capacity = limbCount;
35 		_ptr = jiveMalloc!limb(limbCount);
36 		reset();
37 	}
38 
39 	/** post-blit that does a full copy */
40 	this(this) @trusted
41 	{
42 		auto newPtr = jiveMalloc!limb(limbCount);
43 		newPtr[0 .. limbCount] = _ptr[0 .. limbCount];
44 		_capacity = limbCount;
45 		_ptr = newPtr;
46 	}
47 
48 	/** destructor */
49 	~this()
50 	{
51 		jiveFree(_ptr);
52 		_ptr = null;
53 	}
54 
55 	/** check for emptiness */
56 	bool empty() const @property
57 	{
58 		return _length == 0;
59 	}
60 
61 	/** number of elements */
62 	size_t length() const @property
63 	{
64 		return _length;
65 	}
66 
67 	/** number of elements this structure can hold without further allocations */
68 	size_t capacity() const @property
69 	{
70 		return _capacity * limbBits;
71 	}
72 
73 	/** Allocated heap memory in bytes. */
74 	size_t memUsage() const pure nothrow @property @trusted
75 	{
76 		return limb.sizeof*_capacity;
77 	}
78 
79 	/** make sure this structure can contain given number of elements without further allocs */
80 	void reserve(size_t newCap, bool overEstimate = false) @trusted
81 	{
82 		// bit-count -> limb-count
83 		newCap = (newCap + limbBits - 1) / limbBits;
84 
85 		if(newCap <= _capacity)
86 			return;
87 
88 		if(overEstimate)
89 			newCap = max(newCap, 2*_capacity);
90 
91 		auto newPtr = jiveMalloc!limb(newCap);
92 		newPtr[0..limbCount] = _ptr[0..limbCount];
93 		newPtr[limbCount..newCap] = 0;
94 
95 		jiveFree(_ptr);
96 		_ptr = newPtr;
97 		_capacity = newCap;
98 	}
99 
100 	inout(limb)* ptr() inout
101 	{
102 		return _ptr;
103 	}
104 
105 	/** number of limbs in use */
106 	size_t limbCount() const @property
107 	{
108 		return (_length + limbBits - 1) / limbBits;
109 	}
110 
111 	/** returns limbs in use */
112 	inout(limb)[] limbs() inout @property @trusted
113 	{
114 		return _ptr[0 .. limbCount];
115 	}
116 
117 	/** either cuts of or fills new elements with false */
118 	void resize(size_t size)
119 	{
120 		reserve(size);
121 
122 		// reset cut of elements. TODO: optimize
123 		for(size_t i = size; i < _length; ++i)
124 			this[i] = false;
125 
126 		_length = size;
127 	}
128 
129 	/** count number of elements equal to v */
130 	size_t count(bool v) const @trusted
131 	{
132 		// NOTE: relies on unused bits being zero
133 		size_t c;
134 		foreach(x; (cast(uint*)_ptr)[0..(_length+31)/32])
135 			c += popcnt(x); // why is there only a 32 bit version of popcnt in core.bitop?
136 		if(v)
137 			return c;
138 		else
139 			return _length-c;
140 	}
141 
142 	/** set all elements to false */
143 	void reset()
144 	{
145 		limbs[] = 0;
146 	}
147 
148 	/** indexing */
149 	bool opIndex(string file = __FILE__, int line = __LINE__)(size_t i) const @trusted
150 	{
151 		if(boundsChecks && (i >= length))
152 			assert(false, boundsCheckMsg!(file, line));
153 		return bt(ptr, i) != 0; // why does bt return an int (and not a bool)?
154 	}
155 
156 	/** ditto */
157 	void opIndexAssign(string file = __FILE__, int line = __LINE__)(bool v, size_t i) @trusted
158 	{
159 		if(boundsChecks && (i >= length))
160 			assert(false, boundsCheckMsg!(file, line));
161 		if(v)
162 			bts(ptr, i);
163 		else
164 			btr(ptr, i);
165 	}
166 
167 	/** toggle element i, returns old value. */
168 	bool toggle(string file = __FILE__, int line = __LINE__)(size_t i) @trusted
169 	{
170 		if(boundsChecks && (i >= length))
171 			assert(false, boundsCheckMsg!(file, line));
172 		return btc(ptr, i) != 0;
173 	}
174 
175 	// TODO: efficient bitwise operations and some kind of iteration
176 }
177 
178 @nogc nothrow pure @safe unittest
179 {
180 	auto a = BitArray(5);
181 	a[1] = true;
182 	a.toggle(2);
183 	a.resize(6);
184 	assert(a[5] == false);
185 	assert(a.count(true) == 2);
186 	assert(a.count(false) == 4);
187 }