1 /++ 2 This module contains classes that are related to data storage 3 +/ 4 module utils.lists; 5 6 import std.file; 7 import std.stdio; 8 import utils.misc; 9 10 /// Use to manage dynamic arrays that frequently change lengths 11 /// 12 /// Provides more functionality for arrays, like searching in arrays, removing elements... 13 class List(T){ 14 private: 15 T[] list; 16 uinteger taken=0; 17 uinteger extraAlloc; 18 public: 19 /// constructor 20 /// 21 /// extraCount is the number of extra space to make for new elements when making "room" for new elements 22 this(uinteger extraCount = 4){ 23 extraAlloc = extraCount; 24 } 25 /// appends an element to the list 26 void add(T dat){ 27 if (taken==list.length){ 28 list.length+=extraAlloc; 29 } 30 taken++; 31 list[taken-1] = dat; 32 } 33 /// appends an array to the list 34 void addArray(T[] dat){ 35 list.length = taken; 36 list ~= dat; 37 taken += dat.length; 38 } 39 /// Changes the value of element at an index. 40 /// 41 /// `dat` is the new data 42 void set(uinteger index, T dat){ 43 list[index]=dat; 44 } 45 /// Removes last elements(s) starting from an index; number of elements to remove is in `count` 46 void remove(uinteger index, uinteger count=1){ 47 integer i; 48 integer till=taken-count; 49 for (i=index;i<till;i++){ 50 list[i] = list[i+count]; 51 } 52 list.length-=count; 53 taken-=count; 54 } 55 /// Removes last elements(s); number of elements to remove is in `count` 56 void removeLast(uinteger count = 1){ 57 taken -= count; 58 if (list.length-taken>extraAlloc){ 59 list.length=taken; 60 } 61 } 62 /// shrinks the size of the list, removing last elements. 63 void shrink(uinteger newSize){ 64 if (newSize < taken){ 65 list.length=newSize; 66 taken = list.length; 67 } 68 } 69 /// Inserts an array into this list 70 void insert(uinteger index, T[] dat){ 71 integer i; 72 T[] ar,ar2; 73 ar=list[0..index]; 74 ar2=list[index..taken]; 75 list.length=0; 76 list=ar~dat~ar2; 77 taken+=dat.length; 78 } 79 /// Inserts an element into this list 80 void insert(uinteger index, T dat){ 81 integer i; 82 T[] ar,ar2; 83 ar=list[0..index]; 84 ar2=list[index..taken]; 85 list=(ar~[dat]~ar2).dup; 86 taken++; 87 } 88 /// Writes the list to a file. 89 /// 90 /// `sp` is the line separator. In case of strings, you want it to be `"\n"`; 91 void saveFile(string s, T sp){ 92 File f = File(s,"w"); 93 uinteger i; 94 for (i=0;i<taken;i++){ 95 f.write(list[i],sp); 96 } 97 f.close; 98 } 99 /// Reads an element at an index 100 T read(uinteger index){ 101 return list[index]; 102 } 103 /// Read a slice from the list. 104 /// 105 /// The slice is copied to avoid data in list from getting changed 106 T[] readRange(uinteger index,uinteger i2){ 107 T[] r; 108 r = list[index .. i2].dup; 109 return r; 110 } 111 /// Reads the last element in list. 112 T readLast(){ 113 return list[taken-1]; 114 } 115 /// returns last elements in list. number of elements to return is specified in `count` 116 T[] readLast(uinteger count){ 117 T[] r; 118 r = list[taken-count..taken].dup; 119 return r; 120 } 121 /// length of the list 122 @property integer length(){ 123 return taken; 124 } 125 /// Exports this list into a array 126 T[] toArray(){ 127 return list.dup; 128 } 129 /// Loads array into this list 130 void loadArray(T[] newList){ 131 uinteger i; 132 list = newList.dup; 133 taken = newList.length; 134 } 135 /// empties the list 136 void clear(){ 137 list = []; 138 taken = 0; 139 } 140 /// Returns index of the first matching element. -1 if not found 141 /// 142 /// `dat` is the element to search for 143 /// `i` is the index from where to start, default is 0 144 /// `forward` if true, searches in a forward direction, from lower index to higher 145 integer indexOf(bool forward=true)(T dat, integer i=0){ 146 static if (forward){ 147 for (;i<taken;i++){ 148 if (list[i]==dat){break;} 149 if (i==taken-1){i=-1;break;} 150 } 151 }else{ 152 for (;i>=0;i--){ 153 if (list[i]==dat){break;} 154 if (i==0){i=-1;break;} 155 } 156 } 157 if (taken==0){ 158 i=-1; 159 } 160 return i; 161 } 162 } 163 /// Unittest for List 164 unittest{ 165 List!ubyte list = new List!ubyte; 166 //`List.insert` and `List.add` and `List.toArray` 167 list.add(0); 168 list.add(1); 169 list.insert(1, 2); 170 assert(list.toArray() == [0, 2, 1]); 171 //`List.indexOf` 172 assert(list.indexOf(1) == 2); 173 //`List.clear` 174 list.clear; 175 assert(list.length == 0); 176 //`List.loadArray` 177 list.loadArray([0, 1, 2, 3]); 178 assert(list.length == 4); 179 assert(list.indexOf(3) == 3); 180 //`List.addArray` 181 list.addArray([4, 5, 6, 7, 8]); 182 assert(list.length == 9); 183 //`List.set` and `List.read` 184 list.set(0, 1); 185 assert(list.read(0) == 1); 186 //`List.readLast` 187 assert(list.readLast() == 8); 188 assert(list.readLast(2) == [7, 8]); 189 //`List.readRange` 190 assert(list.readRange(0, 2) == [1, 1]); 191 //`List.remove` 192 list.remove(0, 2); 193 assert(list.read(0) == 2); 194 //`List.removeLast` 195 list.removeLast(2); 196 assert(list.readLast() == 6); 197 198 destroy(list); 199 } 200 201 /// A basic stack with push, and pop 202 class Stack(T){ 203 private: 204 struct stackItem(T){ 205 T data; /// the data this item holds 206 stackItem* prev; /// pointer to previous stackItem 207 } 208 stackItem!(T)* lastItemPtr; 209 uinteger itemCount; 210 public: 211 this(){ 212 lastItemPtr = null; 213 itemCount = 0; 214 } 215 ~this(){ 216 clear; 217 } 218 /// Appends an item to the stack 219 void push(T item){ 220 stackItem!(T)* newItem = new stackItem!T; 221 (*newItem).data = item; 222 (*newItem).prev = lastItemPtr; 223 lastItemPtr = newItem; 224 //increase count 225 itemCount ++; 226 } 227 /// Appends an array of items to the stack 228 void push(T[] items){ 229 // put them all in stackItem[] 230 stackItem!(T)*[] newItems; 231 newItems.length = items.length; 232 for (uinteger i = 0; i < items.length; i ++){ 233 newItems[i] = new stackItem!T; 234 (*newItems[i]).data = items[i]; 235 } 236 // make them all point to their previous item, except for the first one, which should point to `lastItemPtr` 237 for (uinteger i = newItems.length - 1; i > 0; i --){ 238 (*newItems[i]).prev = newItems[i-1]; 239 } 240 (*newItems[0]).prev = lastItemPtr; 241 lastItemPtr = newItems[newItems.length - 1]; 242 //increase count 243 itemCount += newItems.length; 244 } 245 /// Reads and removes an item from the stack, if no more items are present, throws Exception 246 T pop(){ 247 // make sure its not null 248 if (lastItemPtr !is null){ 249 T r = (*lastItemPtr).data; 250 // delete it from stack 251 stackItem!(T)* prevItem = (*lastItemPtr).prev; 252 destroy(*lastItemPtr); 253 lastItemPtr = prevItem; 254 //decrease count 255 itemCount --; 256 return r; 257 }else{ 258 throw new Exception("Cannot pop from empty stack"); 259 } 260 } 261 /// Reads and removes an array of items from the stack, 262 /// if not enough items are left, throws Exception 263 /// 264 /// count is the number of elements to return 265 /// `reverse`, if true, elements are read in reverse, last-pushed is last in array 266 T[] pop(bool reverse=false)(uinteger count){ 267 //make sure there are enough items 268 if (itemCount >= count){ 269 T[] r; 270 r.length = count; 271 stackItem!(T)* ptr = lastItemPtr; 272 static if (reverse){ 273 for (integer i = count-1; i >= 0; i --){ 274 r[i] = (*ptr).data; 275 ptr = (*ptr).prev; 276 // delete this item 277 .destroy(*lastItemPtr); 278 lastItemPtr = ptr; 279 } 280 }else{ 281 for (uinteger i = 0; i < count; i ++){ 282 r[i] = (*ptr).data; 283 ptr = (*ptr).prev; 284 //delete it 285 .destroy(*lastItemPtr); 286 lastItemPtr = ptr; 287 } 288 } 289 //decrease count 290 itemCount -= r.length; 291 return r; 292 }else{ 293 throw new Exception("Not enough items in stack"); 294 } 295 } 296 /// Empties the stack 297 void clear(){ 298 // go through all items and delete em 299 stackItem!(T)* ptr; 300 ptr = lastItemPtr; 301 while (ptr !is null){ 302 stackItem!(T)* prevPtr = (*ptr).prev; 303 destroy(*ptr); 304 ptr = prevPtr; 305 } 306 lastItemPtr = null; 307 itemCount = 0; 308 } 309 /// Number of items in stack 310 @property uinteger count(){ 311 return itemCount; 312 } 313 } 314 /// Unittests for Stack 315 unittest{ 316 Stack!ubyte stack = new Stack!ubyte; 317 //`Stack.push` and `Stack.pop` 318 stack.push(0); 319 stack.push([1, 2]); 320 assert(stack.pop == 2); 321 assert(stack.pop(2) == [1, 0]); 322 stack.push([1, 0]); 323 assert(stack.pop!(true)(2) == [1, 0]); 324 //`Stack.clear` && `Stack.count` 325 stack.push(0); 326 assert(stack.count == 1); 327 stack.clear; 328 assert(stack.count == 0); 329 stack.destroy; 330 } 331 332 /// A linked list, used where only reading in the forward direction is required 333 class LinkedList(T){ 334 private: 335 ///represents an item in a linked list. contains the item, and pointer to the next item's container 336 struct LinkedItem(T){ 337 T data; 338 LinkedItem!(T)* next = null;//mark it null to show the list has ended 339 } 340 LinkedItem!(T)* firstItemPtr; 341 LinkedItem!(T)* lastItemPtr;//the pointer of the last item, used for appending new items 342 LinkedItem!(T)* nextReadPtr;//the pointer of the next item to be read 343 LinkedItem!(T)* lastReadPtr;//the pointer to the last item that was read 344 345 uinteger itemCount;//stores the total number of items 346 347 LinkedItem!(T)*[uinteger] bookmarks; 348 public: 349 this(){ 350 firstItemPtr = null; 351 lastItemPtr = null; 352 nextReadPtr = null; 353 lastReadPtr = null; 354 itemCount = 0; 355 } 356 ~this(){ 357 //free all the memory occupied 358 clear(); 359 } 360 ///clears/resets the list. Frees all the occupied memory, & removes all items 361 void clear(){ 362 //make sure that the list is populated 363 if (firstItemPtr !is null){ 364 LinkedItem!(T)* nextPtr; 365 for (nextReadPtr = firstItemPtr; nextReadPtr !is null; nextReadPtr = nextPtr){ 366 nextPtr = (*nextReadPtr).next; 367 destroy(*nextReadPtr); 368 } 369 //reset all variables 370 firstItemPtr = null; 371 lastItemPtr = null; 372 nextReadPtr = null; 373 lastReadPtr = null; 374 itemCount = 0; 375 } 376 } 377 ///adds a new node at the end of the list 378 void append(T item){ 379 LinkedItem!(T)* ptr = new LinkedItem!(T); 380 (*ptr).data = item; 381 (*ptr).next = null; 382 //add it to the list 383 if (firstItemPtr is null){ 384 firstItemPtr = ptr; 385 nextReadPtr = firstItemPtr; 386 }else{ 387 (*lastItemPtr).next = ptr; 388 } 389 //mark this item as last 390 lastItemPtr = ptr; 391 //increase item count 392 itemCount ++; 393 } 394 ///adds new nodes at end of list from an array 395 void append(T[] items){ 396 if (items.length > 0){ 397 LinkedItem!(T)*[] newNodes; 398 newNodes.length = items.length; 399 // put nodes inside the LinkedItem list 400 for (uinteger i = 0; i < items.length; i++){ 401 newNodes[i] = new LinkedItem!T; 402 (*newNodes[i]).data = items[i]; 403 } 404 // make them point to their next node 405 for (uinteger i = 0, end = newNodes.length-1; i < end; i ++){ 406 (*newNodes[i]).next = newNodes[i+1]; 407 } 408 // make last item from newNodes point to null 409 (*newNodes[newNodes.length-1]).next = null; 410 // make the last item point to first item in newNodes 411 if (firstItemPtr is null){ 412 firstItemPtr = newNodes[0]; 413 nextReadPtr = newNodes[0]; 414 }else{ 415 (*lastItemPtr).next = newNodes[0]; 416 } 417 // mark the last item in newNodes as last in list 418 lastItemPtr = newNodes[newNodes.length-1]; 419 //increase count 420 itemCount += newNodes.length; 421 } 422 } 423 ///removes the first node in list 424 void removeFirst(){ 425 //make sure list is populated 426 if (firstItemPtr !is null){ 427 LinkedItem!(T)* first; 428 first = firstItemPtr; 429 //mark the second item as first, if there isn't a second item, it'll automatically be marked null 430 firstItemPtr = (*firstItemPtr).next; 431 //if nextReadPtr is firstItemPtr, move it to next as well 432 if (nextReadPtr is first){ 433 nextReadPtr = firstItemPtr; 434 } 435 // if the last-read is pointing to first item, null it 436 if (lastReadPtr is first){ 437 lastItemPtr = null; 438 } 439 //free memory occupied by first 440 destroy(*first); 441 //decrease count 442 itemCount --; 443 } 444 } 445 ///removes the node that was last read using `LinkedList.read`. The last node cannot be removed using this. 446 ///returns true on success 447 /// 448 ///It works by moving contents of next item into the last-read one, and removing the next node 449 bool removeLastRead(){ 450 bool r = false; 451 if (lastReadPtr !is null){ 452 LinkedItem!(T)* thisItem = lastReadPtr;// the item to delete 453 LinkedItem!(T)* nextItem = (*thisItem).next;// the item after last read 454 // make sure that the item to be deleted isn't last 455 if (nextItem !is null){ 456 // move contents of next to this item 457 thisItem.data = nextItem.data; 458 // set the pointer to the item after next 459 thisItem.next = nextItem.next; 460 // if nextItem is last item, move last item pointer to thisItem 461 if (nextItem is lastItemPtr){ 462 lastItemPtr = thisItem; 463 } 464 // delete nextItem 465 destroy(*nextItem); 466 467 r = true; 468 }else{ 469 // if there is only one item, or the pointer to second-last item is available, then it can be deleted 470 if (itemCount == 1){ 471 // just clearing the list will do the job 472 this.clear(); 473 // but we must increase the item count because at the end, it will be deceased by one 474 itemCount ++;// a workaround... 475 r = true; 476 }else{ 477 //we'll have to read till second-last item to get be able to remove the last item 478 LinkedItem!(T)* item = firstItemPtr; 479 for (uinteger i = 0, end = itemCount-2; i < end; i ++){ 480 item = item.next; 481 } 482 // now `item` is pointing to second last item, make sure this is true 483 if (item.next == lastItemPtr){ 484 //make the list end here 485 item.next = null; 486 // destroy last one 487 destroy(*lastItemPtr); 488 lastItemPtr = item; 489 490 r = true; 491 }/*else{ 492 something that shouldn't have gone wrong went wrong with `LinkedList.itemCount` 493 }*/ 494 495 } 496 } 497 } 498 //decrease count 499 if (r){ 500 itemCount --; 501 //since the last-read has been removed, null that pointer, to prevent segFault 502 lastReadPtr = null; 503 504 } 505 return r; 506 } 507 ///number of items that the list is holding 508 @property uinteger count(){ 509 return itemCount; 510 } 511 ///resets the read position, i.e: set reading position to first node, and nulls the last-read-ptr 512 void resetRead(){ 513 nextReadPtr = firstItemPtr; 514 lastReadPtr = null; 515 } 516 ///returns pointer of next node to be read, null if there are no more nodes 517 T* read(){ 518 T* r; 519 if (nextReadPtr !is null){ 520 r = &((*nextReadPtr).data); 521 //mark this item as last read 522 lastReadPtr = nextReadPtr; 523 //move read position 524 nextReadPtr = (*nextReadPtr).next; 525 }else{ 526 r = null; 527 lastReadPtr = null; 528 } 529 return r; 530 } 531 /// Returns the pointer to the first node in the list 532 T* readFirst(){ 533 if (firstItemPtr !is null){ 534 lastReadPtr = firstItemPtr; 535 return &((*firstItemPtr).data); 536 }else{ 537 return null; 538 } 539 } 540 /// Returns the pointer to the last node in the list 541 T* readLast(){ 542 if (lastItemPtr !is null){ 543 lastReadPtr = lastItemPtr; 544 return &((*lastItemPtr).data); 545 }else{ 546 return null; 547 } 548 } 549 /// Reads the list into an array, and returns the array 550 T[] toArray(){ 551 LinkedItem!(T)* currentNode = firstItemPtr; 552 uinteger i = 0; 553 T[] r; 554 r.length = itemCount; 555 while (currentNode !is null){ 556 r[i] = (*currentNode).data; 557 // move to next node 558 currentNode = (*currentNode).next; 559 i ++; 560 } 561 return r; 562 } 563 /// Inserts a node after the position of last-read-node 564 /// To insert at beginning, call `resetRead` before inserting 565 /// 566 /// For inserting more than one nodes, use `LinkedList.insertNodes` 567 void insert(T node){ 568 LinkedItem!(T)* newNode = new LinkedItem!T; 569 (*newNode).data = node; 570 // check if has to insert at beginning or at after last-read 571 if (lastReadPtr !is null){ 572 // make new node point to the current next-to-be-read node 573 (*newNode).next = lastReadPtr.next; 574 // make last read node point to new node 575 (*lastReadPtr).next = newNode; 576 }else{ 577 // make this item point to first-item 578 (*newNode).next = firstItemPtr; 579 // mark this as first item 580 firstItemPtr = newNode; 581 } 582 // make next read point to this node now 583 nextReadPtr = newNode; 584 //increase count 585 itemCount ++; 586 } 587 /// Inserts an array of nodes after the position of last-read-node 588 /// If there is no last-read-item, the item is inserted at beginning. To do this, call `resetRead` before inserting 589 /// Returns true on success, false on failure 590 void insert(T[] nodes){ 591 if (nodes.length > 0){ 592 LinkedItem!(T)*[] newNodes; 593 newNodes.length = nodes.length; 594 // put nodes inside the LinkedItem list 595 for (uinteger i = 0; i < nodes.length; i++){ 596 newNodes[i] = new LinkedItem!T; 597 (*newNodes[i]).data = nodes[i]; 598 } 599 // make them point to their next node 600 for (uinteger i = 0, end = newNodes.length-1; i < end; i ++){ 601 (*newNodes[i]).next = newNodes[i+1]; 602 } 603 // check if has to insert at beginning or at after last-read 604 if (lastReadPtr !is null && nodes.length > 0){ 605 // and make the last node in list point to the node after last-read 606 (*newNodes[newNodes.length-1]).next = (*lastReadPtr).next; 607 // make last read node point to the first new-node 608 (*lastReadPtr).next = newNodes[0]; 609 }else{ 610 // insert at beginning 611 (*newNodes[newNodes.length-1]).next = firstItemPtr; 612 // make this the first node 613 firstItemPtr = newNodes[0]; 614 } 615 //make next read point to this 616 nextReadPtr = newNodes[0]; 617 //increase count 618 itemCount += nodes.length; 619 } 620 } 621 /// Returns true if list contains a node, i.e searches for a node and returns true if found 622 bool hasElement(T node){ 623 bool r = false; 624 LinkedItem!(T)* currentNode = firstItemPtr; 625 while (currentNode !is null){ 626 if ((*currentNode).data == node){ 627 r = true; 628 break; 629 } 630 // move to next node 631 currentNode = (*currentNode).next; 632 } 633 return r; 634 } 635 /// Returns true if list contains all elements provided in an array, else, false 636 /// 637 /// returns false if the array contains the same elements at more than one index 638 bool hasElements(T[] nodes){ 639 bool r = false; 640 nodes = nodes.dup; 641 // go through the list and match as many elements as possible 642 LinkedItem!(T)* currentNode = firstItemPtr; 643 while (currentNode !is null){ 644 // check if current node matches any in array 645 integer index = nodes.indexOf((*currentNode).data); 646 if (index >= 0){ 647 // this node matched, so remove it from the array 648 nodes = nodes.deleteElement(index); 649 } 650 // check if all elements have been checked against 651 if (nodes.length == 0){ 652 break; 653 } 654 // move to next node 655 currentNode = (*currentNode).next; 656 } 657 // Now check if the nodes array is empty, if yes, then all nodes were matched 658 if (nodes.length == 0){ 659 r = true; 660 } 661 return r; 662 } 663 /// Sets a "bookmark", and returns the bookmark-ID, throws Exception if there is no last-read-item to place bookmark on 664 /// 665 /// this ID can later be used to go back to the reading position at which the bookmark was placed 666 uinteger placeBookmark(){ 667 if (lastReadPtr is null){ 668 throw new Exception("no last-read-item to place bookmark on"); 669 }else{ 670 // go through bookmarks list to find empty slot, or create a new one 671 uinteger id = 0; 672 while (true){ 673 if (id in bookmarks){ 674 id ++; 675 }else{ 676 break; 677 } 678 } 679 bookmarks[id] = lastReadPtr.next; 680 return id; 681 } 682 } 683 /// moves read/insert position back to a bookmark using the bookmark ID 684 /// Does NOT delete the bookmark. Use `LinkedList.removeBookmark` to delete 685 /// Retutns true if successful 686 /// false if the bookmark ID no longer exists 687 bool moveToBookmark(uinteger id){ 688 if (id !in bookmarks){ 689 return false; 690 }else{ 691 nextReadPtr = bookmarks[id]; 692 return true; 693 } 694 } 695 /// removes a bookmark using the bookmark id 696 /// returns true if successful 697 /// false if the bookmark doesn't exist 698 bool removeBookmark(uinteger id){ 699 if (id !in bookmarks){ 700 return false; 701 }else{ 702 bookmarks.remove(id); 703 return true; 704 } 705 } 706 /// Removes all bookmarks 707 void clearBookmarks(){ 708 foreach(key; bookmarks.keys){ 709 bookmarks.remove(key); 710 } 711 } 712 } 713 /// Unittests for `utils.lists.LinkedList` 714 unittest{ 715 LinkedList!ubyte list = new LinkedList!ubyte; 716 //`LinkedList.append` and `LinkedList.read` and `LinkedList.readFirst` and `LinkedList.readLast` and `LinkedList.resetRead` 717 list.append(0); 718 list.append(1); 719 list.append(2); 720 assert(*(list.readFirst()) == 0); 721 assert(*(list.readLast()) == 2); 722 assert(list.count == 3); 723 list.read();// to skip, we wanna read the node at index 1 (2nd node) 724 assert(*(list.read()) == 1); 725 list.resetRead(); 726 assert(*(list.read()) == 0); 727 // `LinkedList.append(T[])`: 728 list.clear(); 729 list.append(0); 730 list.append([1, 2, 3]); 731 assert(list.count == 4); 732 assert(list.toArray ==[0, 1, 2, 3]); 733 list.clear; 734 list.append([0, 1, 2]); 735 list.append(3); 736 assert(list.count == 4); 737 assert(list.toArray == [0, 1, 2, 3]); 738 //`LinkedList.clear` 739 list.clear(); 740 list.append(3); 741 list.append(4); 742 assert(*(list.read()) == 3); 743 assert(list.count == 2); 744 list.clear(); 745 //`LinkedList.removeLastRead` and `Linkedlist.removeFirst` 746 list.append(0); 747 list.append(1); 748 list.append(2); 749 list.read(); 750 list.read(); 751 list.removeLastRead(); 752 list.resetRead(); 753 assert(*(list.read()) == 0); 754 assert(*(list.read()) == 2); 755 assert(list.count == 2); 756 list.removeFirst(); 757 list.resetRead(); 758 assert(*(list.read()) == 2); 759 assert(list.count == 1); 760 list.removeLastRead(); 761 assert(list.count == 0); 762 //`LinkedList.toArray` and `LinkedList.insertNode` and `LinkedList.insertNodes` 763 list.clear();// to reset stuff 764 list.append(0); 765 list.append(4); 766 list.read(); 767 list.insert(1); 768 assert(*(list.read()) == 1); 769 list.insert([2, 3]); 770 list.resetRead(); 771 assert(list.count == 5); 772 assert(list.toArray == [0, 1, 2, 3, 4]); 773 //`Linkedlist.hasElement` and `LinkedList.hasElements` 774 assert(list.hasElement(0) == true); 775 assert(list.hasElement(4) == true); 776 assert(list.hasElement(5) == false); 777 assert(list.hasElement(7) == false); 778 assert(list.hasElements([3, 1, 2, 0, 4]) == true); 779 assert(list.hasElements([0, 1, 2, 6]) == false); 780 // `LinkedList.insert` at beginning 781 list.clear; 782 list.insert([1, 2]); 783 list.insert(0); 784 assert(list.count == 3); 785 assert(list.toArray == [0, 1, 2]); 786 //destroying last item 787 list.clear(); 788 list.append(0); 789 list.append(1); 790 list.append(2); 791 list.read(); 792 list.read(); 793 list.read(); 794 assert(list.removeLastRead() == true); 795 assert(list.toArray() == [0, 1]); 796 //bookmarks 797 list.clear; 798 list.append([0, 1, 2, 3, 4, 5]); 799 assert(*list.read == 0); 800 assert(*list.read == 1); 801 assert(*list.read == 2); 802 { 803 uinteger id = list.placeBookmark; 804 assert(*list.read == 3); 805 assert(list.moveToBookmark(id + 1) == false); 806 assert(list.moveToBookmark(id) == true); 807 assert(*list.read == 3); 808 assert(list.removeBookmark(id) == true); 809 } 810 destroy(list); 811 } 812 813 /// Used in logging widgets. Holds upto certain number of elements, after which older elements are over-written 814 class LogList(T){ 815 private: 816 List!T list; 817 uinteger readFrom, maxLen; 818 public: 819 this(uinteger maxLength=100){ 820 list = new List!T; 821 readFrom = 0; 822 maxLen = maxLength; 823 } 824 ~this(){ 825 delete list; 826 } 827 ///adds an item to the log 828 void add(T dat){ 829 if (list.length>=maxLen){ 830 list.set(readFrom,dat); 831 readFrom++; 832 }else{ 833 list.add(dat); 834 } 835 } 836 ///Returns array containing items, in first-added-last order 837 T[] read(uinteger count=0){ 838 T[] r; 839 if (count>list.length){ 840 count = list.length; 841 } 842 if (count > 0){ 843 uinteger i; 844 if (count>list.length){ 845 count = list.length; 846 } 847 r.length = count; 848 for (i = readFrom; i < count; i++){ 849 r[i] = list.read((readFrom+i)%count); 850 } 851 }else{ 852 r = null; 853 } 854 return r; 855 } 856 ///resets and clears the log 857 void reset(){ 858 list.clear; 859 readFrom = 0; 860 } 861 ///returns the max number of items that can be stored 862 @property uinteger maxCapacity(){ 863 return maxLen; 864 } 865 } 866 867 /// For reading files. Can also be used for writing 868 class FileReader{ 869 private: 870 ubyte[] stream = null; 871 uinteger seekPos = 0; 872 public: 873 /// If filename is not null, attempts to load file into memory 874 this(string filename=null){ 875 if (filename != null){ 876 this.loadFile(filename); 877 } 878 } 879 ~this(){ 880 // destructor, nothing to do yet 881 } 882 /// loads file into memory, throws exception if fails 883 void loadFile(string filename){ 884 stream = cast(ubyte[])std.file.read(filename); 885 seekPos = 0; 886 } 887 /// Writes the stream to a file, throws exception if fails 888 void saveFile(string filename){ 889 std.file.write(filename, cast(void[])stream.dup); 890 } 891 892 /// reads and returns `size` number of bytes from file starting from seek-position 893 /// If not enough bytes are left, the array returned will be smaller than `size` 894 /// Returns null if the seek-position is at end, or if there are no bytes to be read 895 void[] read(uinteger size=1){ 896 // check if `size` number of chars are left 897 if (size + seekPos <= stream.length){ 898 void[] r = stream[seekPos .. seekPos+size].dup; 899 seekPos += size; 900 return r; 901 }else if (seekPos < stream.length){ 902 void[] r = stream[seekPos .. stream.length].dup; 903 seekPos = stream.length; 904 return r; 905 }else{ 906 // nothing left to read, return null 907 return null; 908 } 909 } 910 /// Reads and returns bytes starting from seek till `terminate` byte, or if EOF is reached 911 void[] read(ubyte terminate){ 912 uinteger readFrom = seekPos; 913 while (seekPos < stream.length){ 914 if (stream[seekPos] == terminate){ 915 seekPos ++;// to include the terminate byte in result 916 break; 917 } 918 seekPos ++; 919 } 920 return stream[readFrom .. seekPos].dup; 921 } 922 /// Writes an array at the seek-position, and moves seek to end of the written data 923 void write(ubyte[] t){ 924 if (seekPos > stream.length){ 925 throw new Exception("failed to write data to stream. Seek is out of stream.length"); 926 }else if (seekPos == stream.length){ 927 // just append to end of stream 928 stream = stream ~ t.dup; 929 seekPos = stream.length; 930 }else{ 931 // insert it in the middle 932 stream = stream[0 .. seekPos] ~ t ~ stream[seekPos .. stream.length]; 933 seekPos += t.length; 934 } 935 } 936 /// Writes a byte at the seek-position, and moves seek to end of the written data 937 void write(ubyte t){ 938 write([t]); 939 } 940 /// Removes a number of bytes starting from the seek-position 941 /// 942 /// Returns true if it was able to remove some bytes, false if not 943 bool remove(uinteger count = 1){ 944 // make sure there are enough bytes to remove 945 if (seekPos + count > stream.length){ 946 // remove as much as possible 947 if (seekPos >= stream.length){ 948 return false; 949 }else{ 950 stream = stream[0 .. seekPos-1]; 951 } 952 }else{ 953 stream = stream[0 .. seekPos-1] ~ stream[seekPos + count .. stream.length]; 954 } 955 return true; 956 } 957 /// Clears the stream, resets the stream 958 void clear(){ 959 stream.length = 0; 960 seekPos = 0; 961 } 962 /// The seek position, from where the next char(s) will be read, or written to 963 @property uinteger seek(){ 964 return seekPos; 965 } 966 /// The seek position, from where the next char(s) will be read, or written to 967 @property uinteger seek(uinteger newSeek){ 968 if (newSeek > stream.length){ 969 return seekPos = stream.length; 970 }else{ 971 return seekPos = newSeek; 972 } 973 } 974 /// The size of file in bytes, read-only 975 @property uinteger size(){ 976 return stream.length; 977 } 978 } 979 /// unittests for FileReader 980 unittest{ 981 // file loading/ saving not tested 982 FileReader stream = new FileReader(); 983 assert(stream.seek == 0); 984 // write & read 985 stream.write(cast(ubyte[])"ABCE"); 986 assert(stream.seek == 4); 987 assert(stream.size == 4); 988 stream.seek = 3; 989 stream.write(cast(ubyte)'D'); 990 assert(stream.size == 5); 991 assert(stream.seek == 4); 992 stream.seek = 0; 993 assert(stream.read(stream.size) == cast(ubyte[])"ABCDE"); 994 stream.seek = 0; 995 assert(stream.read(cast(ubyte)'C') == cast(ubyte[])"ABC"); 996 stream.seek = 0; 997 assert(stream.read(cast(ubyte)'Z') == cast(ubyte[])"ABCDE"); 998 // clear 999 stream.clear; 1000 assert(stream.size == 0); 1001 assert(stream.seek == 0); 1002 // remove 1003 stream.write(cast(ubyte[])"ABCDE"); 1004 stream.seek = 3; 1005 assert(stream.remove(99) == true); 1006 stream.seek = 0; 1007 assert(stream.read(cast(ubyte)'Z') == cast(ubyte[])"AB"); 1008 stream.write(cast(ubyte[])"CDE"); 1009 stream.seek = 1; 1010 assert(stream.remove(2) == true); 1011 stream.seek = 0; 1012 assert(stream.read(cast(ubyte)'Z') == "DE"); 1013 }