1 /** 2 License: public domain 3 Authors: Simon Bürger 4 */ 5 6 module jive.array; 7 8 import jive.internal; 9 import core.exception : RangeError; 10 import core.stdc.string : memmove, memcpy, memset; 11 import std.algorithm; 12 import std.conv : emplace; 13 import std.format; 14 import std.range; 15 import std.traits; 16 17 18 /** 19 * Array of dynamic size. 20 * 21 * If you add elements, new memory will be allocated automatically as needed. 22 * Typically there is more memory allocated than is currently in use. There is 23 * a tradeoff between wasted space and frequency of reallocations. The default 24 * behaviour is to double the capacity every time the allocated memory is 25 * filled up. This ensures that pushBack takes O(1) in amortized time. If you 26 * know the number of elements in advance, you can use reserve to avoid 27 * reallocations, but this is just an optimization and never necessary. 28 */ 29 struct Array(V) 30 { 31 private V* _ptr = null; // unused elements are undefined 32 private size_t _capacity = 0; // size of buf 33 private size_t _length = 0; // used size 34 35 /** constructor for given length */ 36 this(size_t size) 37 { 38 resize(size); 39 } 40 41 /** constructor for given length and init */ 42 this(size_t size, V val) 43 { 44 resize(size, val); 45 } 46 47 /** constructor that gets content from arbitrary range */ 48 this(Stuff)(Stuff data) 49 if(isInputRange!Stuff && is(ElementType!Stuff:V)) 50 { 51 static if(hasLength!Stuff) 52 reserve(_length + data.length); 53 54 foreach(ref x; data) 55 pushBack(x); 56 } 57 58 /** post-blit that does a full copy */ 59 this(this) 60 { 61 auto newPtr = jiveMalloc!V(_length); 62 63 static if(hasElaborateCopyConstructor!V) 64 { 65 for(size_t i = 0; i < _length; ++i) 66 emplace(newPtr + i, _ptr[i]); 67 } 68 else 69 memcpy(newPtr, _ptr, V.sizeof * _length); 70 _ptr = newPtr; 71 _capacity = _length; 72 } 73 74 /** destructor */ 75 ~this() 76 { 77 static if (hasElaborateDestructor!V) 78 foreach (ref x; this[]) 79 destroy(x); 80 jiveFree(_ptr); 81 _ptr = null; // probably not necessary, just a precaution 82 } 83 84 /** check for emptiness */ 85 bool empty() const pure nothrow @property @safe 86 { 87 return _length == 0; 88 } 89 90 /** number of elements */ 91 size_t length() const pure nothrow @property @safe 92 { 93 return _length; 94 } 95 96 /** ditto */ 97 size_t opDollar() const pure nothrow @property @safe 98 { 99 return _length; 100 } 101 102 /** number of elements this structure can hold without further allocations */ 103 size_t capacity() const pure nothrow @property @safe 104 { 105 return _capacity; 106 } 107 108 /** make sure this structure can contain given number of elements without further allocs */ 109 void reserve(size_t newCap, bool overEstimate = false) nothrow @trusted 110 { 111 if(newCap <= _capacity) 112 return; 113 114 if(overEstimate) 115 newCap = max(newCap, 2*_capacity); 116 117 auto newPtr = jiveMalloc!V(newCap); 118 memcpy(newPtr, _ptr, V.sizeof * _length); 119 120 static if(hasIndirections!V) 121 memset(newPtr + length, 0, V.sizeof * (newCap - _length)); // prevent false pointers 122 123 jiveFree(_ptr); 124 _ptr = newPtr; 125 _capacity = newCap; 126 } 127 128 /** pointer to the first element */ 129 inout(V)* ptr() inout pure nothrow @property @safe 130 { 131 return _ptr; 132 } 133 134 /** default range */ 135 inout(V)[] opSlice() inout nothrow pure @trusted 136 { 137 return _ptr[0 .. _length]; 138 } 139 140 /** subrange */ 141 inout(V)[] opSlice(string file = __FILE__, int line = __LINE__)(size_t a, size_t b) inout pure nothrow @trusted 142 { 143 if(boundsChecks && (a > b || b > _length)) 144 assert(false, boundsCheckMsg!(file, line)); 145 return _ptr[a .. b]; 146 } 147 148 /** assign all elements to the same value */ 149 void opSliceAssign(V v) 150 { 151 this.opSlice()[] = v; 152 } 153 154 /* assign a subset of elements to the same value */ 155 void opSliceAssign(string file = __FILE__, int line = __LINE__)(V v, size_t a, size_t b) 156 { 157 this.opSlice!(file, line)(a, b)[] = v; 158 } 159 160 /** indexing */ 161 ref inout(V) opIndex(string file = __FILE__, int line = __LINE__)(size_t i) inout pure nothrow @trusted 162 { 163 if(boundsChecks && i >= _length) 164 assert(false, boundsCheckMsg!(file, line)); 165 return _ptr[i]; 166 } 167 168 /** first element, same as this[0] */ 169 ref inout(V) front(string file = __FILE__, int line = __LINE__)() inout pure nothrow 170 { 171 return this.opIndex!(file, line)(0); 172 } 173 174 /** last element, same as this[$-1] */ 175 ref inout(V) back(string file = __FILE__, int line = __LINE__)() inout pure nothrow 176 { 177 return this.opIndex!(file, line)(_length-1); 178 } 179 180 /** add some new element to the back */ 181 void pushBack(V val) @trusted 182 { 183 reserve(_length + 1, true); 184 moveEmplace(val, _ptr[_length]); 185 ++_length; 186 } 187 188 /** add multiple new elements to the back */ 189 void pushBack(Stuff)(Stuff data) 190 if(!is(Stuff:V) && isInputRange!Stuff && is(ElementType!Stuff:V)) 191 { 192 static if(hasLength!Stuff) 193 reserve(_length + data.length, true); 194 195 foreach(ref x; data) 196 pushBack(x); 197 } 198 199 /** convenience alias for pushBack */ 200 alias pushBack opCatAssign; 201 202 /** returns removed element */ 203 V popBack(string file = __FILE__, int line = __LINE__)() @trusted 204 { 205 if(boundsChecks && empty) 206 assert(false, boundsCheckMsg!(file, line)); 207 208 --_length; 209 V r = void; 210 memcpy(&r, _ptr + _length, V.sizeof); 211 static if(hasIndirections!V) 212 memset(_ptr + _length, 0, V.sizeof); 213 return r; 214 } 215 216 /** insert new element at given location. moves all elements behind */ 217 void insert(string file = __FILE__, int line = __LINE__)(size_t i, V data) @trusted 218 { 219 if(boundsChecks && i > _length) 220 assert(false, boundsCheckMsg!(file, line)); 221 222 reserve(_length + 1, true); 223 memmove(_ptr + i + 1, _ptr + i, V.sizeof * (_length - i)); 224 ++_length; 225 moveEmplace(data, _ptr[i]); 226 } 227 228 /** remove i'th element. moves all elements behind */ 229 V remove(string file = __FILE__, int line = __LINE__)(size_t i) @trusted 230 { 231 if(boundsChecks && i >= _length) 232 assert(false, boundsCheckMsg!(file, line)); 233 234 V r = void; 235 memcpy(&r, _ptr + i, V.sizeof); 236 --_length; 237 memmove(_ptr + i, _ptr + i + 1, V.sizeof * (_length - i)); 238 static if(hasIndirections!V) 239 memset(_ptr + _length, 0, V.sizeof); 240 return r; 241 } 242 243 /** sets the size to some value. Either cuts of some values (but does not free memory), or fills new ones with V.init */ 244 void resize(size_t size, V v) @trusted 245 { 246 // TODO: remove @trusted in case V's destructor is @system 247 248 if(size <= _length) // shrink 249 { 250 static if(hasElaborateDestructor!V) 251 for(size_t i = size; i < _length; ++i) 252 destroy(_ptr[i]); 253 static if(hasIndirections!V) 254 memset(_ptr + size, 0, V.sizeof * (_length - size)); 255 _length = size; 256 } 257 else // expand 258 { 259 reserve(size, false); 260 for(size_t i = _length; i < size; ++i) 261 emplace(_ptr + i, v); 262 _length = size; 263 } 264 } 265 266 /** ditto */ 267 void resize(size_t size) 268 { 269 resize(size, V.init); 270 } 271 272 /** sets the size and fills everything with one value */ 273 void assign(size_t newsize, V v) 274 { 275 resize(newsize); 276 this[] = v; 277 } 278 279 /** remove all content but keep allocated memory (same as resize(0)) */ 280 void clear() 281 { 282 resize(0); 283 } 284 285 /** convert to string */ 286 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 287 { 288 formatValue(sink, this[], fmt); 289 } 290 291 /** ditto */ 292 string toString() const 293 { 294 return format("%s", this[]); 295 } 296 297 hash_t toHash() const nothrow @trusted 298 { 299 return this[].hashOf; 300 } 301 302 bool opEquals(const ref Array other) const 303 { 304 return equal(this[], other[]); 305 } 306 307 static if(__traits(compiles, V.init < V.init)) 308 int opCmp(const ref Array other) const 309 { 310 return cmp(this[], other[]); 311 } 312 } 313 314 /// 315 /+@nogc+/ nothrow pure @safe unittest 316 { 317 Array!int a; 318 319 a.pushBack(1); 320 a.pushBack([2,3,4,5]); 321 assert(a.popBack() == 5); 322 assert(equal(a[], [1,2,3,4])); 323 324 a[] = 0; 325 a[1..3] = 1; 326 a.resize(6, 2); 327 assert(equal(a[], [0,1,1,0,2,2])); 328 } 329 330 // check for all nice attributes in case the type is nice as well 331 @nogc nothrow pure @safe unittest 332 { 333 struct S1 334 { 335 int x; 336 //int* p; // FIXME: when GC.addRange/removeRange become pure 337 this(this) @nogc nothrow pure @safe {} 338 ~this() @nogc nothrow pure @safe {} 339 } 340 341 static assert(hasElaborateDestructor!S1); 342 static assert(hasElaborateCopyConstructor!S1); 343 //static assert(hasIndirections!S); 344 345 Array!S1 a; 346 S1 s; 347 a.pushBack(s); 348 a.popBack(); 349 a.resize(5); 350 a.insert(3, s); 351 a.remove(3); 352 a.reserve(10); 353 a[] = s; 354 a[0..1] = s; 355 a.pushBack(a[0]); 356 a.pushBack(a[0..1]); // only valid with the previous reserve! 357 } 358 359 // check correct invocation of postblit/destructor 360 unittest 361 { 362 int counter = 0; 363 364 struct S 365 { 366 bool active; 367 this(bool active) { this.active = active; if(active) ++counter; } 368 this(this) { if(active) ++counter; } 369 ~this() { if(active) --counter; } 370 } 371 372 { 373 Array!S a; 374 assert(counter == 0); 375 a.pushBack(S(true)); 376 assert(counter == 1); 377 a.pushBack(a[0]); 378 assert(counter == 2); 379 a.reserve(5); 380 a.pushBack(a[]); 381 382 assert(counter == 4); 383 a.insert(1, a[1]); 384 assert(counter == 5); 385 a.remove(3); 386 assert(counter == 4); 387 Array!S b = a; 388 assert(a[] == b[]); 389 assert(counter == 8); 390 } 391 assert(counter == 0); 392 } 393 394 // check move-semantics 395 unittest 396 { 397 struct S3 398 { 399 int x; 400 alias x this; 401 this(this) { assert(x == 0); } 402 } 403 404 Array!S3 a; 405 a.pushBack(S3(1)); 406 a.pushBack(S3(3)); 407 a.insert(1, S3(2)); 408 a.popBack(); 409 a[1] = S3(4); 410 assert(a[] == [S3(1),S3(4)]); 411 } 412 413 // type with no @safe/pure/etc-attributes at all and also no opCmp 414 unittest 415 { 416 struct S 417 { 418 int* x; 419 this(this){ } 420 ~this(){ } 421 bool opEquals(const S b) const { return x is b.x; } 422 } 423 424 static assert(hasIndirections!S); 425 static assert(hasElaborateDestructor!S); 426 427 S s; 428 Array!S a; 429 a.pushBack(s); 430 a.pushBack([s,s]); 431 a.popBack(); 432 a.reserve(5); 433 a.insert(1, s); 434 a.remove(2); 435 a.resize(3); 436 assert(a[] == [s,s,s]); 437 Array!S b = a; 438 }