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 /** 109 * Allocated heap memory in bytes. 110 * This is recursive if V has a `.memUsage` property. Otherwise it is equal 111 * to `V.sizeof * capacity` 112 */ 113 size_t memUsage() const pure nothrow @property @trusted 114 { 115 size_t r = V.sizeof*_capacity; 116 static if(hasMember!(V, "memUsage")) 117 for(size_t i = 0; i < _length; ++i) 118 r += _ptr[i].memUsage; 119 return r; 120 } 121 122 /** make sure this structure can contain given number of elements without further allocs */ 123 void reserve(size_t newCap, bool overEstimate = false) nothrow @trusted 124 { 125 if(newCap <= _capacity) 126 return; 127 128 if(overEstimate) 129 newCap = max(newCap, 2*_capacity); 130 131 auto newPtr = jiveMalloc!V(newCap); 132 memcpy(newPtr, _ptr, V.sizeof * _length); 133 134 static if(hasIndirections!V) 135 memset(newPtr + length, 0, V.sizeof * (newCap - _length)); // prevent false pointers 136 137 jiveFree(_ptr); 138 _ptr = newPtr; 139 _capacity = newCap; 140 } 141 142 /** pointer to the first element */ 143 inout(V)* ptr() inout pure nothrow @property @safe 144 { 145 return _ptr; 146 } 147 148 /** default range */ 149 inout(V)[] opSlice() inout nothrow pure @trusted 150 { 151 return _ptr[0 .. _length]; 152 } 153 154 /** subrange */ 155 inout(V)[] opSlice(string file = __FILE__, int line = __LINE__)(size_t a, size_t b) inout pure nothrow @trusted 156 { 157 if(boundsChecks && (a > b || b > _length)) 158 assert(false, boundsCheckMsg!(file, line)); 159 return _ptr[a .. b]; 160 } 161 162 /** assign all elements to the same value */ 163 void opSliceAssign(V v) 164 { 165 this.opSlice()[] = v; 166 } 167 168 /* assign a subset of elements to the same value */ 169 void opSliceAssign(string file = __FILE__, int line = __LINE__)(V v, size_t a, size_t b) 170 { 171 this.opSlice!(file, line)(a, b)[] = v; 172 } 173 174 /** indexing */ 175 ref inout(V) opIndex(string file = __FILE__, int line = __LINE__)(size_t i) inout pure nothrow @trusted 176 { 177 if(boundsChecks && i >= _length) 178 assert(false, boundsCheckMsg!(file, line)); 179 return _ptr[i]; 180 } 181 182 /** first element, same as this[0] */ 183 ref inout(V) front(string file = __FILE__, int line = __LINE__)() inout pure nothrow 184 { 185 return this.opIndex!(file, line)(0); 186 } 187 188 /** last element, same as this[$-1] */ 189 ref inout(V) back(string file = __FILE__, int line = __LINE__)() inout pure nothrow 190 { 191 return this.opIndex!(file, line)(_length-1); 192 } 193 194 /** add some new element to the back */ 195 void pushBack(V val) @trusted 196 { 197 reserve(_length + 1, true); 198 moveEmplace(val, _ptr[_length]); 199 ++_length; 200 } 201 202 /** add multiple new elements to the back */ 203 void pushBack(Stuff)(Stuff data) 204 if(!is(Stuff:V) && isInputRange!Stuff && is(ElementType!Stuff:V)) 205 { 206 static if(hasLength!Stuff) 207 reserve(_length + data.length, true); 208 209 foreach(ref x; data) 210 pushBack(x); 211 } 212 213 /** convenience alias for pushBack */ 214 alias pushBack opCatAssign; 215 216 /** returns removed element */ 217 V popBack(string file = __FILE__, int line = __LINE__)() @trusted 218 { 219 if(boundsChecks && empty) 220 assert(false, boundsCheckMsg!(file, line)); 221 222 --_length; 223 V r = void; 224 memcpy(&r, _ptr + _length, V.sizeof); 225 static if(hasIndirections!V) 226 memset(_ptr + _length, 0, V.sizeof); 227 return r; 228 } 229 230 /** insert new element at given location. moves all elements behind */ 231 void insert(string file = __FILE__, int line = __LINE__)(size_t i, V data) @trusted 232 { 233 if(boundsChecks && i > _length) 234 assert(false, boundsCheckMsg!(file, line)); 235 236 reserve(_length + 1, true); 237 memmove(_ptr + i + 1, _ptr + i, V.sizeof * (_length - i)); 238 ++_length; 239 moveEmplace(data, _ptr[i]); 240 } 241 242 /** remove i'th element. moves all elements behind */ 243 V remove(string file = __FILE__, int line = __LINE__)(size_t i) @trusted 244 { 245 if(boundsChecks && i >= _length) 246 assert(false, boundsCheckMsg!(file, line)); 247 248 V r = void; 249 memcpy(&r, _ptr + i, V.sizeof); 250 --_length; 251 memmove(_ptr + i, _ptr + i + 1, V.sizeof * (_length - i)); 252 static if(hasIndirections!V) 253 memset(_ptr + _length, 0, V.sizeof); 254 return r; 255 } 256 257 /** sets the size to some value. Either cuts of some values (but does not free memory), or fills new ones with V.init */ 258 void resize(size_t size, V v) @trusted 259 { 260 // TODO: remove @trusted in case V's destructor is @system 261 262 if(size <= _length) // shrink 263 { 264 static if(hasElaborateDestructor!V) 265 for(size_t i = size; i < _length; ++i) 266 destroy(_ptr[i]); 267 static if(hasIndirections!V) 268 memset(_ptr + size, 0, V.sizeof * (_length - size)); 269 _length = size; 270 } 271 else // expand 272 { 273 reserve(size, false); 274 for(size_t i = _length; i < size; ++i) 275 emplace(_ptr + i, v); 276 _length = size; 277 } 278 } 279 280 /** ditto */ 281 void resize(size_t size) 282 { 283 resize(size, V.init); 284 } 285 286 /** sets the size and fills everything with one value */ 287 void assign(size_t newsize, V v) 288 { 289 resize(newsize); 290 this[] = v; 291 } 292 293 /** remove all content but keep allocated memory (same as resize(0)) */ 294 void clear() 295 { 296 resize(0); 297 } 298 299 /** convert to string */ 300 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 301 { 302 formatValue(sink, this[], fmt); 303 } 304 305 /** ditto */ 306 string toString() const 307 { 308 return format("%s", this[]); 309 } 310 311 hash_t toHash() const nothrow @trusted 312 { 313 return this[].hashOf; 314 } 315 316 bool opEquals(const ref Array other) const 317 { 318 return equal(this[], other[]); 319 } 320 321 static if(__traits(compiles, V.init < V.init)) 322 int opCmp(const ref Array other) const 323 { 324 return cmp(this[], other[]); 325 } 326 } 327 328 /// 329 /+@nogc+/ nothrow pure @safe unittest 330 { 331 Array!int a; 332 333 a.pushBack(1); 334 a.pushBack([2,3,4,5]); 335 assert(a.popBack() == 5); 336 assert(equal(a[], [1,2,3,4])); 337 338 a[] = 0; 339 a[1..3] = 1; 340 a.resize(6, 2); 341 assert(equal(a[], [0,1,1,0,2,2])); 342 } 343 344 // check for all nice attributes in case the type is nice as well 345 @nogc nothrow pure @safe unittest 346 { 347 struct S1 348 { 349 int x; 350 //int* p; // FIXME: when GC.addRange/removeRange become pure 351 this(this) @nogc nothrow pure @safe {} 352 ~this() @nogc nothrow pure @safe {} 353 } 354 355 static assert(hasElaborateDestructor!S1); 356 static assert(hasElaborateCopyConstructor!S1); 357 //static assert(hasIndirections!S); 358 359 Array!S1 a; 360 S1 s; 361 a.pushBack(s); 362 a.popBack(); 363 a.resize(5); 364 a.insert(3, s); 365 a.remove(3); 366 a.reserve(10); 367 a[] = s; 368 a[0..1] = s; 369 a.pushBack(a[0]); 370 a.pushBack(a[0..1]); // only valid with the previous reserve! 371 } 372 373 // check correct invocation of postblit/destructor 374 unittest 375 { 376 int counter = 0; 377 378 struct S 379 { 380 bool active; 381 this(bool active) { this.active = active; if(active) ++counter; } 382 this(this) { if(active) ++counter; } 383 ~this() { if(active) --counter; } 384 } 385 386 { 387 Array!S a; 388 assert(counter == 0); 389 a.pushBack(S(true)); 390 assert(counter == 1); 391 a.pushBack(a[0]); 392 assert(counter == 2); 393 a.reserve(5); 394 a.pushBack(a[]); 395 396 assert(counter == 4); 397 a.insert(1, a[1]); 398 assert(counter == 5); 399 a.remove(3); 400 assert(counter == 4); 401 Array!S b = a; 402 assert(a[] == b[]); 403 assert(counter == 8); 404 } 405 assert(counter == 0); 406 } 407 408 // check move-semantics 409 unittest 410 { 411 struct S3 412 { 413 int x; 414 alias x this; 415 this(this) { assert(x == 0); } 416 } 417 418 Array!S3 a; 419 a.pushBack(S3(1)); 420 a.pushBack(S3(3)); 421 a.insert(1, S3(2)); 422 a.popBack(); 423 a[1] = S3(4); 424 assert(a[] == [S3(1),S3(4)]); 425 } 426 427 // type with no @safe/pure/etc-attributes at all and also no opCmp 428 unittest 429 { 430 struct S 431 { 432 int* x; 433 this(this){ } 434 ~this(){ } 435 bool opEquals(const S b) const { return x is b.x; } 436 } 437 438 static assert(hasIndirections!S); 439 static assert(hasElaborateDestructor!S); 440 441 S s; 442 Array!S a; 443 a.pushBack(s); 444 a.pushBack([s,s]); 445 a.popBack(); 446 a.reserve(5); 447 a.insert(1, s); 448 a.remove(2); 449 a.resize(3); 450 assert(a[] == [s,s,s]); 451 Array!S b = a; 452 } 453 454 // check capacity and memUsage 455 unittest 456 { 457 Array!int a; 458 assert(a.capacity == 0); 459 assert(a.memUsage == 0); 460 a.reserve(10); 461 assert(a.capacity == 10); 462 assert(a.memUsage == 40); 463 464 Array!(Array!int) b; 465 b.reserve(10); 466 b.pushBack(Array!int([1])); 467 b.pushBack(Array!int([1,2])); 468 b.pushBack(Array!int([1,2,3])); 469 b.pushBack(Array!int([1,2,3,4])); 470 b.pushBack(Array!int([1,2,3,4,5])); 471 assert(b.capacity == 10); 472 assert(b[0].capacity == 1); 473 assert(b[1].capacity == 2); 474 assert(b[2].capacity == 3); 475 assert(b[3].capacity == 4); 476 assert(b[4].capacity == 5); 477 assert(b.memUsage == 10*Array!int.sizeof + 4*(1+2+3+4+5)); 478 } 479 480 struct Prune(V) 481 { 482 @disable this(); 483 @disable this(this); 484 485 Array!V* arr; 486 487 this(ref Array!V arr) 488 { 489 this.arr = &arr; 490 } 491 492 int opApply(int delegate(size_t i, ref V val, ref bool remove) dg) 493 { 494 size_t a = 0; 495 size_t b = 0; 496 int r = 0; 497 498 while(b < arr.length && r == 0) 499 { 500 bool remove = false; 501 r = dg(b, (*arr)[b], remove); 502 503 if(!remove) 504 { 505 if(a != b) 506 (*arr)[a] = move((*arr)[b]); 507 ++a; 508 } 509 510 ++b; 511 } 512 513 if(a == b) 514 return r; 515 516 while(b < arr.length) 517 (*arr)[a++] = move((*arr)[b++]); 518 519 arr._length = a; 520 return r; 521 } 522 523 int opApply(int delegate(ref V val, ref bool remove) dg) 524 { 525 size_t a = 0; 526 size_t b = 0; 527 int r = 0; 528 529 while(b < arr.length && r == 0) 530 { 531 bool remove = false; 532 r = dg((*arr)[b], remove); 533 534 if(!remove) 535 { 536 if(a != b) 537 (*arr)[a] = move((*arr)[b]); 538 ++a; 539 } 540 541 ++b; 542 } 543 544 if(a == b) 545 return r; 546 547 while(b < arr.length) 548 (*arr)[a++] = move((*arr)[b++]); 549 550 arr._length = a; 551 return r; 552 } 553 } 554 555 Prune!V prune(V)(ref Array!V arr) 556 { 557 return Prune!V(arr); 558 } 559 560 /// 561 unittest 562 { 563 auto a = Array!int([10,20,30,40,50]); 564 565 /* Iterate over an Array. If `remove` is set inside the loop body, the 566 current element is removed from the array. This is more efficient than 567 multiple calls to the `.remove()` method. */ 568 foreach(i, ref x, ref bool rem; prune(a)) 569 { 570 if(x == 30) 571 x = 31; 572 rem = (i == 1) || (x == 40); 573 } 574 575 assert(equal(a[], [10,31,50])); 576 577 /** same, but without indices */ 578 foreach(ref x, ref bool rem; prune(a)) 579 { 580 if(x == 10) 581 x = 11; 582 rem = (x == 50); 583 } 584 585 assert(equal(a[], [11,31])); 586 }