1 /* 2 Copyright (c) 2015-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Dynamic (expandable) array with random access 31 * 32 * Copyright: Timur Gafarov 2015-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov, Roman Vlasov, Andrey Penechko, Eugene Wissner, Roman Chistokhodov, aferust, ijet 35 */ 36 module dlib.container.array; 37 38 import std.traits; 39 import dlib.core.memory; 40 41 /** 42 * GC-free dynamic array implementation. 43 * Very efficient for small-sized arrays. 44 */ 45 struct Array(T, size_t chunkSize = 32) 46 { 47 private: 48 T[chunkSize] staticStorage; 49 T[] dynamicStorage; 50 uint numChunks = 0; 51 uint pos = 0; 52 53 /** 54 * Get pointer to stored data 55 */ 56 private T* storage() nothrow 57 { 58 if (numChunks == 0) 59 return staticStorage.ptr; 60 else 61 return dynamicStorage.ptr; 62 } 63 64 private const(T)* readOnlyStorage() const nothrow 65 { 66 if (numChunks == 0) 67 return staticStorage.ptr; 68 else 69 return dynamicStorage.ptr; 70 } 71 72 private void addChunk() 73 { 74 if (numChunks == 0) 75 { 76 dynamicStorage = New!(T[])(chunkSize); 77 } 78 else 79 { 80 reallocateArray( 81 dynamicStorage, 82 dynamicStorage.length + chunkSize); 83 } 84 numChunks++; 85 } 86 87 /// 88 unittest 89 { 90 Array!int arr; 91 scope(exit) arr.free(); 92 93 assert(arr.length == 0); 94 arr.addChunk(); 95 assert(arr.length == 0); 96 } 97 98 public: 99 /** 100 * Returns true if the array uses dynamic memory. 101 */ 102 @property bool isDynamic() const 103 { 104 return dynamicStorage.length > 0; 105 } 106 107 /** 108 * Preallocate memory without resizing. 109 */ 110 void reserve(const(size_t) amount) 111 { 112 if (amount > pos && amount > staticStorage.length) 113 { 114 if (numChunks == 0) 115 { 116 dynamicStorage = New!(T[])(amount); 117 foreach(i, v; staticStorage) 118 dynamicStorage[i] = v; 119 } 120 else 121 { 122 reallocateArray(dynamicStorage, amount); 123 } 124 125 numChunks = cast(uint)(amount / 32 + amount % 32); 126 } 127 } 128 129 /// 130 unittest 131 { 132 Array!int arr; 133 arr.reserve(100); 134 assert(arr.length == 0); 135 assert(arr.dynamicStorage.length == 100); 136 } 137 138 /** 139 * Resize array and initialize newly added elements with initValue. 140 */ 141 void resize(const(size_t) newLen, T initValue) 142 { 143 if (newLen > pos) 144 { 145 reserve(newLen); 146 for(size_t i = pos; i < newLen; i++) 147 { 148 storage[i] = initValue; 149 } 150 } 151 152 pos = cast(uint)newLen; 153 } 154 155 /// 156 unittest 157 { 158 Array!int arr; 159 arr.resize(100, 1); 160 assert(arr.length == 100); 161 assert(arr[50] == 1); 162 } 163 164 /** 165 * Shift contents of array to the right. 166 * It inreases the size of array by 1. 167 * The first element becomes default initialized. 168 */ 169 void shiftRight() 170 { 171 insertBack(T.init); 172 173 for(uint i = pos-1; i > 0; i--) 174 { 175 storage[i] = storage[i-1]; 176 } 177 } 178 179 /// 180 unittest 181 { 182 Array!int arr; 183 scope(exit) arr.free(); 184 185 arr.shiftRight(); 186 assert(arr.length == 1); 187 assert(arr[0] == int.init); 188 189 arr[0] = 1; 190 arr.insertBack([2,3]); 191 192 arr.shiftRight(); 193 assert(arr.length == 4); 194 assert(arr[0] == 1); 195 assert(arr[1] == 1); 196 assert(arr[2] == 2); 197 assert(arr[3] == 3); 198 } 199 200 /** 201 * Shift contents of array to the left by n positions. 202 * Does not change the size of array. 203 * n of last elements becomes default initialized. 204 */ 205 void shiftLeft(const(uint) n) 206 { 207 for(uint i = 0; i < pos; i++) 208 { 209 if (n + i < pos) 210 storage[i] = storage[n + i]; 211 else 212 storage[i] = T.init; 213 } 214 } 215 216 /// 217 unittest 218 { 219 Array!int arr; 220 scope(exit) arr.free(); 221 222 arr.shiftLeft(1); 223 assert(arr.length == 0); 224 225 arr.insertBack([1,2,3,4,5]); 226 227 arr.shiftLeft(2); 228 assert(arr.length == 5); 229 assert(arr[0] == 3); 230 assert(arr[1] == 4); 231 assert(arr[2] == 5); 232 assert(arr[3] == int.init); 233 assert(arr[4] == int.init); 234 } 235 236 /** 237 * Append single element c to the end. 238 */ 239 void insertBack(T c) 240 { 241 if (numChunks == 0) 242 { 243 staticStorage[pos] = c; 244 pos++; 245 if (pos == chunkSize) 246 { 247 addChunk(); 248 foreach(i, ref v; dynamicStorage) 249 v = staticStorage[i]; 250 } 251 } 252 else 253 { 254 if (pos == dynamicStorage.length) 255 addChunk(); 256 257 dynamicStorage[pos] = c; 258 pos++; 259 } 260 } 261 262 /// 263 unittest 264 { 265 Array!int arr; 266 scope(exit) arr.free(); 267 268 foreach(i; 0..16) { 269 arr.insertBack(i); 270 } 271 assert(arr.length == 16); 272 arr.insertBack(16); 273 assert(arr.length == 17); 274 assert(arr[16] == 16); 275 } 276 277 /** 278 * Append element to the start. 279 */ 280 void insertFront(T c) 281 { 282 shiftRight(); 283 storage[0] = c; 284 } 285 286 /// 287 unittest 288 { 289 Array!int arr; 290 scope(exit) arr.free(); 291 292 arr.insertFront(1); 293 arr.insertBack(2); 294 arr.insertFront(0); 295 assert(arr.data == [0,1,2]); 296 } 297 298 /** 299 * Append all elements of slice s to the end. 300 */ 301 void insertBack(const(T)[] s) 302 { 303 foreach(c; s) 304 insertBack(cast(T)c); 305 } 306 307 /// 308 unittest 309 { 310 Array!int arr; 311 scope(exit) arr.free(); 312 313 arr.insertBack([1,2,3,4]); 314 assert(arr.data == [1,2,3,4]); 315 arr.insertBack([5,6,7,8]); 316 assert(arr.data == [1,2,3,4,5,6,7,8]); 317 } 318 319 /** 320 * Append all elements of slice s to the start. 321 */ 322 void insertFront(const(T)[] s) 323 { 324 foreach_reverse(c; s) 325 insertFront(cast(T)c); 326 } 327 328 /// 329 unittest 330 { 331 Array!int arr; 332 scope(exit) arr.free(); 333 334 arr.insertFront([5,6,7,8]); 335 assert(arr.data == [5,6,7,8]); 336 arr.insertFront([1,2,3,4]); 337 assert(arr.data == [1,2,3,4,5,6,7,8]); 338 } 339 340 /** 341 * Same as insertBack, but in operator form. 342 */ 343 auto opOpAssign(string op)(T c) if (op == "~") 344 { 345 insertBack(c); 346 return this; 347 } 348 349 /// 350 unittest 351 { 352 Array!int arr; 353 scope(exit) arr.free(); 354 355 arr ~= 1; 356 arr ~= 2; 357 assert(arr.data == [1,2]); 358 } 359 360 /** 361 * Same as insertBack, but in operator form. 362 */ 363 auto opOpAssign(string op)(const(T)[] s) if (op == "~") 364 { 365 insertBack(s); 366 return this; 367 } 368 369 /// 370 unittest 371 { 372 Array!int arr; 373 scope(exit) arr.free(); 374 375 arr ~= [1,2,3]; 376 assert(arr.data == [1,2,3]); 377 } 378 379 /** 380 * Remove n of elements from the end. 381 * Returns: number of removed elements. 382 */ 383 uint removeBack(const(uint) n) 384 { 385 if (pos == n) 386 { 387 pos = 0; 388 return n; 389 } 390 else if (pos >= n) 391 { 392 pos -= n; 393 return n; 394 } 395 else 396 { 397 uint res = pos; 398 pos = 0; 399 return res; 400 } 401 } 402 403 /// 404 unittest 405 { 406 Array!int arr; 407 scope(exit) arr.free(); 408 409 arr.insertBack([1,2,3]); 410 assert(arr.removeBack(3) == 3); 411 assert(arr.length == 0); 412 413 arr.insertBack([1,2,3,4]); 414 assert(arr.removeBack(2) == 2); 415 assert(arr.data == [1,2]); 416 417 assert(arr.removeBack(3) == 2); 418 assert(arr.length == 0); 419 } 420 421 /** 422 * Remove n of elements from the start. 423 * Returns: number of removed elements. 424 */ 425 uint removeFront(const(uint) n) 426 { 427 if (pos == n) 428 { 429 pos = 0; 430 return n; 431 } 432 else if (pos > n) 433 { 434 shiftLeft(n); 435 pos -= n; 436 return n; 437 } 438 else 439 { 440 uint res = pos; 441 pos = 0; 442 return res; 443 } 444 } 445 446 /// 447 unittest 448 { 449 Array!int arr; 450 scope(exit) arr.free(); 451 452 arr.insertBack([1,2,3]); 453 assert(arr.removeFront(3) == 3); 454 455 arr.insertBack([1,2,3,4]); 456 assert(arr.removeFront(2) == 2); 457 assert(arr.data == [3,4]); 458 459 assert(arr.removeFront(3) == 2); 460 assert(arr.length == 0); 461 } 462 463 /** 464 * Inserts an element by a given index 465 * (resizing an array and shifting elements). 466 */ 467 void insertKey(const(size_t) i, T v) 468 { 469 if (i < pos) 470 { 471 T* s = storage(); 472 473 insertBack(T.init); 474 475 for (size_t p = pos-1; p > i; p--) 476 { 477 s[p] = s[p-1]; 478 } 479 480 s[i] = v; 481 } 482 } 483 484 unittest 485 { 486 Array!int arr; 487 scope(exit) arr.free(); 488 489 arr.insertBack([1, 4, 5]); 490 491 arr.insertKey(1, 7); 492 assert(arr.length == 4); 493 assert(arr.data == [1, 7, 4, 5]); 494 } 495 496 /** 497 * Removes an element by a given index. 498 */ 499 void removeKey(const(size_t) i) 500 { 501 if (i < pos) 502 { 503 T* s = storage(); 504 for (size_t p = i+1; p <= pos; p++) 505 { 506 s[p-1] = s[p]; 507 } 508 509 pos--; 510 } 511 } 512 513 unittest 514 { 515 Array!int arr; 516 scope(exit) arr.free(); 517 518 arr.insertBack([1, 4, 5]); 519 520 arr.removeKey(1); 521 assert(arr.length == 2); 522 assert(arr.data == [1, 5]); 523 } 524 525 alias insertAt = insertKey; 526 alias removeAt = removeKey; 527 528 /** 529 * If obj is in array, remove its first occurence and return true. 530 * Otherwise do nothing and return false. 531 */ 532 bool removeFirst(T obj) 533 { 534 size_t index; 535 bool found = false; 536 537 for (size_t i = 0; i < data.length; i++) 538 { 539 T o = data[i]; 540 541 static if (isArray!T) 542 { 543 if (o[] == obj[]) 544 { 545 index = i; 546 found = true; 547 break; 548 } 549 } 550 else 551 { 552 if (o is obj) 553 { 554 index = i; 555 found = true; 556 break; 557 } 558 } 559 } 560 561 if (found) 562 { 563 removeKey(index); 564 return true; 565 } 566 567 return false; 568 } 569 570 // For backward compatibility 571 alias append = insertBack; 572 alias appendLeft = insertFront; 573 alias remove = removeBack; 574 alias removeLeft = removeFront; 575 576 /** 577 * Get number of elements in array. 578 */ 579 size_t length() const nothrow 580 { 581 return pos; 582 } 583 584 alias opDollar = length; 585 586 /// 587 unittest 588 { 589 Array!int arr; 590 scope(exit) arr.free(); 591 592 arr.insertBack([1,2,3]); 593 assert(arr.length == 3); 594 } 595 596 /** 597 * Get slice of data 598 */ 599 T[] data() nothrow 600 { 601 return storage[0..pos]; 602 } 603 604 const(T)[] readOnlyData() const nothrow 605 { 606 return readOnlyStorage[0..pos]; 607 } 608 609 /// 610 unittest 611 { 612 Array!(int,4) arr; 613 scope(exit) arr.free(); 614 615 foreach(i; 0..6) { 616 arr.insertBack(i); 617 } 618 619 assert(arr.data == [0,1,2,3,4,5]); 620 } 621 622 /** 623 * Access element by index. 624 */ 625 T opIndex(const(size_t) index) 626 { 627 return data[index]; 628 } 629 630 /** 631 * Access by slice. 632 */ 633 T[] opSlice(const(size_t) start, const(size_t) end) 634 { 635 return data[start..end]; 636 } 637 638 /** 639 * Set element t for index. 640 */ 641 T opIndexAssign(T t, const(size_t) index) 642 { 643 data[index] = t; 644 return t; 645 } 646 647 /** 648 * Iterating over array via foreach. 649 */ 650 int opApply(scope int delegate(size_t i, ref T) dg) 651 { 652 int result = 0; 653 654 foreach(i, ref v; data) 655 { 656 result = dg(i, v); 657 if (result) 658 break; 659 } 660 661 return result; 662 } 663 664 /** 665 * Iterating over array via foreach_reverse. 666 */ 667 int opApplyReverse(scope int delegate(size_t i, ref T) dg) 668 { 669 int result = 0; 670 671 for(size_t i = length; i-- > 0; ) 672 { 673 result = dg(i, data[i]); 674 if (result) 675 break; 676 } 677 678 return result; 679 } 680 681 /// 682 unittest 683 { 684 Array!(int,4) arr; 685 scope(exit) arr.free(); 686 687 int[4] values; 688 arr.insertBack([1,2,3,4]); 689 foreach(i, ref val; arr) { 690 values[i] = val; 691 if(values[i] == 4) { 692 break; 693 } 694 } 695 assert(values[] == arr.data); 696 } 697 698 /** 699 * Iterating over array via foreach. 700 */ 701 int opApply(scope int delegate(ref T) dg) 702 { 703 int result = 0; 704 705 foreach(i, ref v; data) 706 { 707 result = dg(v); 708 if (result) 709 break; 710 } 711 712 return 0; 713 } 714 715 /** 716 * Iterating over array via foreach_reverse. 717 */ 718 int opApplyReverse(scope int delegate(ref T) dg) 719 { 720 int result = 0; 721 722 for(size_t i = length; i-- > 0; ) 723 { 724 result = dg(data[i]); 725 if (result) 726 break; 727 } 728 729 return result; 730 } 731 732 /// 733 unittest 734 { 735 Array!(int,4) arr; 736 scope(exit) arr.free(); 737 738 int[] values; 739 arr.insertBack([1,2,3,4]); 740 foreach(ref val; arr) { 741 values ~= val; 742 } 743 assert(values[] == arr.data); 744 } 745 746 /** 747 * Free dynamically allocated memory used by array. 748 */ 749 void free() 750 { 751 if (dynamicStorage.length) 752 Delete(dynamicStorage); 753 numChunks = 0; 754 pos = 0; 755 } 756 } 757 758 void reallocateArray(T)(ref T[] buffer, const(size_t) len) 759 { 760 T[] buffer2 = New!(T[])(len); 761 for(uint i = 0; i < buffer2.length; i++) 762 if (i < buffer.length) 763 buffer2[i] = buffer[i]; 764 Delete(buffer); 765 buffer = buffer2; 766 } 767 768 /// 769 unittest 770 { 771 auto arr = New!(int[])(3); 772 arr[0] = 1; arr[1] = 2; arr[2] = 3; 773 774 reallocateArray(arr, 2); 775 assert(arr.length == 2); 776 assert(arr[0] == 1); 777 assert(arr[1] == 2); 778 779 reallocateArray(arr, 4); 780 assert(arr.length == 4); 781 assert(arr[0] == 1); 782 assert(arr[1] == 2); 783 }