├── .gitignore ├── ArrayHelper.pas └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | __history/ArrayHelper.pas.~1~ 2 | -------------------------------------------------------------------------------- /ArrayHelper.pas: -------------------------------------------------------------------------------- 1 | unit ArrayHelper; 2 | 3 | 4 | /////////////////////////////////////////////////////////////////////////////// 5 | // 6 | // ArrayHelper version 1.3 7 | // extends class TArray and add TArrayRecord to make dynamic arrays 8 | // as simple, as TList 9 | // 10 | // Copyright(c) 2017 by Willi Commer (wcs) 11 | // Licence GNU 12 | // 13 | // Dynamic arrays are smart because its memore usage is handled by the memory 14 | // manager. But the funtion libraries are lean and differs from object based. 15 | // Based on TArray class, that gives Sort and Binary search, this unit will 16 | // extend TArray with functions available for TList or TStrings. 17 | // The next level is TArrayRecord record type. It wraps a record around 18 | // the dynamic array. This give us the ability to use dynamic arrays like 19 | // objects with out the pain to organize the final Free call. 20 | // 21 | // var 22 | // A: TArrayRecord; 23 | // S: string; 24 | // begin 25 | // A.SetValues(['a','b','c']); 26 | // A.Add('d'); 27 | // assert( A.Count = 4 ); // same as length(A.Items); 28 | // assert( A[1] = 'b' ); 29 | // assert( A.IndexOf('a') = 0 ); 30 | // for S in A do 31 | // .. 32 | // 33 | // For more examples see procedure Test_All_Helper_Functions 34 | // For updates check https://github.com/WilliCommer/ArrayHelper 35 | // 36 | // 37 | // History: 38 | // version 1.3 39 | // Enumeration added 40 | // new functions 'Unique' and 'CopyArray' 41 | // 42 | // version 1.2 43 | // TArrayRecord 44 | // 45 | /////////////////////////////////////////////////////////////////////////////// 46 | 47 | 48 | 49 | { $DEFINE TEST_FUNCTION} // change to active test function 50 | 51 | 52 | interface 53 | uses 54 | System.Classes, System.SysUtils, System.RTLConsts, 55 | System.Generics.Defaults, System.Generics.Collections; 56 | 57 | 58 | type 59 | 60 | // callback function for function ForEach 61 | TArrayForEachCallback = reference to procedure(var Value: T; Index: integer); 62 | 63 | // callback function for function Map 64 | TArrayMapCallback = reference to function(var Value: T; Index: integer): boolean; 65 | 66 | // callback function for function MapTo 67 | TArrayConvert = reference to function(const Value: T): TTo; 68 | 69 | // callback function for function Find 70 | TArrayFindCallback = reference to function(const Value: T): boolean; 71 | 72 | 73 | 74 | // extends class TArray 75 | TArrayHelper = class helper for TArray 76 | // add item to array 77 | class function Add(var Values: TArray; Item: T): integer; static; 78 | 79 | // delete item at index 80 | class procedure Delete(var Values: TArray; Index: integer); static; 81 | 82 | // insert item at index 83 | class procedure Insert(var Values: TArray; Index: integer; Value: T); static; 84 | 85 | // append array 86 | class procedure AddRange(var Values: TArray; const ValuesToInsert: array of T); static; 87 | 88 | // insert array at index 89 | class procedure InsertRange(var Values: TArray; Index: Integer; const ValuesToInsert: array of T); static; 90 | 91 | // get index of equal item 92 | class function IndexOf(var Values: TArray; Item: T): integer; overload; static; 93 | 94 | // get index of equal item (using IComparer) 95 | class function IndexOf(var Values: TArray; Item: T; const Comparer: IComparer): integer; overload; static; 96 | 97 | // get index of maximal item 98 | class function IndexOfMax(var Values: TArray): integer; overload; static; 99 | 100 | // get index of maximal item (using IComparer) 101 | class function IndexOfMax(var Values: TArray; const Comparer: IComparer): integer; overload; static; 102 | 103 | // get index of minimal item 104 | class function IndexOfMin(var Values: TArray): integer; overload; static; 105 | 106 | // get index of minimal item (using IComparer) 107 | class function IndexOfMin(var Values: TArray; const Comparer: IComparer): integer; overload; static; 108 | 109 | // is a equal item is member of values 110 | class function Contains(var Values: TArray; Item: T): boolean; overload; static; 111 | 112 | // is a equal item is member of values (using IComparer) 113 | class function Contains(var Values: TArray; Item: T; const Comparer: IComparer): boolean; overload; static; 114 | 115 | // compare two arrays 116 | class function Compare(const Values, ValuesToCompare: array of T): boolean; overload; static; 117 | 118 | // compare two arrays (using IComparer) 119 | class function Compare(const Values, ValuesToCompare: array of T; const Comparer: IComparer): boolean; overload; static; 120 | 121 | // ForEach 122 | class procedure ForEach(var Values: TArray; const Callback: TArrayForEachCallback); static; 123 | 124 | // find with callback 125 | class function Find(const Values: TArray; const Callback: TArrayFindCallback; const StartIndex: integer = 0): integer; overload; static; 126 | 127 | // return an array filtered and converted by callback function 128 | class function Map(const Values: TArray; const Callback: TArrayMapCallback): TArray; static; 129 | 130 | // return the array as TList 131 | class procedure List(const Values: TArray; var ValList: TList); static; 132 | 133 | {$IFDEF TEST_FUNCTION} 134 | // test, debug and example function 135 | class procedure Test_All_Helper_Functions; 136 | {$ENDIF TEST_FUNCTION} 137 | 138 | end; 139 | 140 | 141 | type 142 | TArrayRecord = record 143 | strict private type 144 | TEnumerator = class 145 | private 146 | FValue: ^TArrayRecord; 147 | FIndex: integer; 148 | function GetCurrent: T; 149 | public 150 | constructor Create(var AValue: TArrayRecord); 151 | function MoveNext: Boolean; 152 | property Current: T read GetCurrent; 153 | end; 154 | public 155 | function GetEnumerator(): TEnumerator; 156 | private 157 | function GetCount: integer; 158 | procedure SetCount(const Value: integer); 159 | function GetItemAt(Index: integer): T; 160 | procedure SetItemAt(Index: integer; Value: T); 161 | public 162 | Items: TArray; 163 | property Count: integer read GetCount write SetCount; 164 | property ItemAt[Index: Integer]: T read GetItemAt write SetItemAt; default; 165 | 166 | constructor Create(ACapacity: integer); overload; 167 | constructor Create(const AValues: array of T); overload; 168 | procedure Clear; 169 | procedure SetItems(const Values: array of T); 170 | function Add(const Value: T): integer; 171 | procedure Delete(Index: integer); 172 | procedure Insert(Index: integer; Value: T); 173 | function Remove(const AItem: T): boolean; 174 | function AddIfNotContains(const AItem: T): boolean; 175 | 176 | procedure AddRange(const ValuesToInsert: array of T); overload; 177 | procedure AddRange(const ValuesToInsert: TArrayRecord); overload; 178 | 179 | procedure InsertRange(Index: Integer; const ValuesToInsert: array of T); overload; 180 | procedure InsertRange(Index: Integer; const ValuesToInsert: TArrayRecord); overload; 181 | 182 | function IndexOf(Item: T): integer; overload; 183 | function IndexOf(Item: T; const Comparer: IComparer): integer; overload; 184 | 185 | function IndexOfMax: integer; overload; 186 | function IndexOfMax(const Comparer: IComparer): integer; overload; 187 | function IndexOfMin: integer; overload; 188 | function IndexOfMin(const Comparer: IComparer): integer; overload; 189 | 190 | function Contains(Item: T): boolean; overload; 191 | function Contains(Item: T; const Comparer: IComparer): boolean; overload; 192 | 193 | function Compare(const ValuesToCompare: array of T): boolean; overload; 194 | function Compare(const ValuesToCompare: array of T; const Comparer: IComparer): boolean; overload; 195 | function Compare(const ValuesToCompare: TArrayRecord): boolean; overload; 196 | function Compare(const ValuesToCompare: TArrayRecord; const Comparer: IComparer): boolean; overload; 197 | 198 | procedure ForEach(const Callback: TArrayForEachCallback); 199 | function Find(const Callback: TArrayFindCallback; const StartIndex: integer = 0): integer; overload; 200 | function Map(const Callback: TArrayMapCallback): TArrayRecord; 201 | function Convert(const Callback: TArrayConvert): TArrayRecord; 202 | 203 | procedure Sort; overload; 204 | procedure Sort(const AComparer: IComparer); overload; 205 | procedure Sort(const AComparer: IComparer; AIndex, ACount: Integer); overload; 206 | function BinarySearch(const AItem: T; out AFoundIndex: Integer; const AComparer: IComparer; 207 | AIndex, ACount: Integer): Boolean; overload; 208 | function BinarySearch(const AItem: T; out AFoundIndex: Integer; const AComparer: IComparer): Boolean; overload; 209 | function BinarySearch(const AItem: T; out AFoundIndex: Integer): Boolean; overload; 210 | 211 | procedure Unique; // remove duplicates 212 | function CopyArray(FromIndex: integer; Count: integer = -1): TArrayRecord; // return array slice 213 | 214 | procedure List(var ValList: TList); 215 | // operator overloads 216 | class operator Equal(const L, R: TArrayRecord): boolean; 217 | class operator NotEqual(const L, R: TArrayRecord): boolean; 218 | end; 219 | 220 | 221 | 222 | implementation 223 | 224 | 225 | { TArrayHelper } 226 | 227 | class function TArrayHelper.Add(var Values: TArray; Item: T): integer; 228 | begin 229 | Result := Length(Values); 230 | SetLength(Values,Result+1); 231 | Values[Result] := Item; 232 | end; 233 | 234 | 235 | class procedure TArrayHelper.Delete(var Values: TArray; Index: integer); 236 | var 237 | I: Integer; 238 | begin 239 | if (Index < Low(Values)) or (Index > High(Values)) then 240 | raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); 241 | for I := Index+1 to High(Values) do 242 | Values[I-1] := Values[I]; 243 | SetLength(Values, length(Values)-1); 244 | end; 245 | 246 | 247 | class procedure TArrayHelper.Insert(var Values: TArray; Index: integer; Value: T); 248 | var 249 | I,H: Integer; 250 | begin 251 | if (Index < Low(Values)) or (Index > length(Values)) then 252 | raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); 253 | H := High(Values); 254 | SetLength(Values, length(Values)+1); 255 | for I := H downto Index do 256 | Values[I+1] := Values[I]; 257 | Values[Index] := Value; 258 | end; 259 | 260 | 261 | class procedure TArrayHelper.InsertRange(var Values: TArray; Index: Integer; const ValuesToInsert: array of T); 262 | var 263 | I,L,H: Integer; 264 | begin 265 | L := length(ValuesToInsert); 266 | if L = 0 then EXIT; 267 | if (Index < Low(Values)) or (Index > length(Values)) then 268 | raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); 269 | H := High(Values); 270 | SetLength(Values, length(Values) + L); 271 | for I := H downto Index do 272 | Values[I+L] := Values[I]; 273 | for I := Low(ValuesToInsert) to High(ValuesToInsert) do 274 | Values[Index+I] := ValuesToInsert[I]; 275 | end; 276 | 277 | 278 | class procedure TArrayHelper.List(const Values: TArray; var ValList: 279 | TList); 280 | var 281 | I: Integer; 282 | begin 283 | if not Assigned(ValList) then 284 | raise Exception.Create('ValList is nil'); 285 | ValList.Clear; 286 | for I := Low(Values) to High(Values) do 287 | ValList.Add(Values[I]); 288 | end; 289 | 290 | class procedure TArrayHelper.AddRange(var Values: TArray; const ValuesToInsert: array of T); 291 | var 292 | I,Index: Integer; 293 | begin 294 | Index := length(Values); 295 | SetLength(Values, length(Values) + length(ValuesToInsert)); 296 | for I := Low(ValuesToInsert) to High(ValuesToInsert) do 297 | Values[Index+I] := ValuesToInsert[I]; 298 | end; 299 | 300 | 301 | class function TArrayHelper.IndexOf(var Values: TArray; Item: T; const Comparer: IComparer): integer; 302 | begin 303 | for Result := Low(Values) to High(Values) do 304 | if Comparer.Compare(Values[Result], Item) = 0 then EXIT; 305 | Result := -1; 306 | end; 307 | 308 | class function TArrayHelper.IndexOf(var Values: TArray; Item: T): integer; 309 | begin 310 | Result := IndexOf(Values, Item, TComparer.Default); 311 | end; 312 | 313 | 314 | class function TArrayHelper.IndexOfMax(var Values: TArray): integer; 315 | begin 316 | Result := IndexOfMax(Values, TComparer.Default); 317 | end; 318 | 319 | class function TArrayHelper.IndexOfMax(var Values: TArray; const Comparer: IComparer): integer; 320 | var 321 | I: Integer; 322 | begin 323 | if length(Values) = 0 then 324 | raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); 325 | Result := 0; 326 | for I := 1 to High(Values) do 327 | if Comparer.Compare(Values[I], Values[Result]) > 0 then 328 | Result := I; 329 | end; 330 | 331 | class function TArrayHelper.IndexOfMin(var Values: TArray): integer; 332 | begin 333 | Result := IndexOfMin(Values, TComparer.Default); 334 | end; 335 | 336 | class function TArrayHelper.IndexOfMin(var Values: TArray; const Comparer: IComparer): integer; 337 | var 338 | I: Integer; 339 | begin 340 | if length(Values) = 0 then 341 | raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); 342 | Result := 0; 343 | for I := 1 to High(Values) do 344 | if Comparer.Compare(Values[I], Values[Result]) < 0 then 345 | Result := I; 346 | end; 347 | 348 | 349 | 350 | 351 | 352 | class function TArrayHelper.Contains(var Values: TArray; Item: T; const Comparer: IComparer): boolean; 353 | begin 354 | Result := IndexOf(Values, Item, Comparer) <> -1; 355 | end; 356 | 357 | 358 | 359 | class function TArrayHelper.Contains(var Values: TArray; Item: T): boolean; 360 | begin 361 | Result := Contains(Values, Item, TComparer.Default); 362 | end; 363 | 364 | 365 | 366 | class function TArrayHelper.Compare(const Values, ValuesToCompare: array of T; const Comparer: IComparer): boolean; 367 | var 368 | I: Integer; 369 | begin 370 | if length(Values) <> length(ValuesToCompare) then EXIT( FALSE ); 371 | for I := Low(Values) to High(Values) do 372 | if Comparer.Compare(Values[I], ValuesToCompare[I]) <> 0 then EXIT( FALSE ); 373 | Result := TRUE; 374 | end; 375 | 376 | 377 | class function TArrayHelper.Compare(const Values, ValuesToCompare: array of T): boolean; 378 | begin 379 | Result := Compare(Values, ValuesToCompare, TComparer.Default); 380 | end; 381 | 382 | 383 | 384 | 385 | class procedure TArrayHelper.ForEach(var Values: TArray; const Callback: TArrayForEachCallback); 386 | var 387 | I: Integer; 388 | begin 389 | for I := Low(Values) to High(Values) do 390 | Callback(Values[I], I); 391 | end; 392 | 393 | 394 | 395 | class function TArrayHelper.Find(const Values: TArray; const Callback: TArrayFindCallback; 396 | const StartIndex: integer): integer; 397 | begin 398 | if (length(Values) = 0) or (StartIndex < 0) or (StartIndex > High(Values)) then EXIT( -1 ); 399 | for Result := StartIndex to High(Values) do 400 | if Callback(Values[Result]) then EXIT; 401 | Result := -1; 402 | end; 403 | 404 | 405 | 406 | class function TArrayHelper.Map(const Values: TArray; const Callback: TArrayMapCallback): TArray; 407 | var 408 | Item: T; 409 | I: Integer; 410 | begin 411 | Result := NIL; 412 | for I := Low(Values) to High(Values) do 413 | begin 414 | Item := Values[I]; 415 | if Callback(Item, I) then 416 | Add(Result, Item); 417 | end; 418 | end; 419 | 420 | 421 | 422 | 423 | 424 | 425 | { TArrayRecord.TEnumerator } 426 | 427 | constructor TArrayRecord.TEnumerator.Create(var AValue: TArrayRecord); 428 | begin 429 | FValue := @AValue; 430 | FIndex := -1; 431 | end; 432 | 433 | function TArrayRecord.TEnumerator.GetCurrent: T; 434 | begin 435 | Result := FValue^.Items[FIndex]; 436 | end; 437 | 438 | function TArrayRecord.TEnumerator.MoveNext: Boolean; 439 | begin 440 | Result := FIndex < High(FValue^.Items); 441 | Inc(FIndex); 442 | end; 443 | 444 | 445 | 446 | { TArrayRecord } 447 | 448 | 449 | constructor TArrayRecord.Create(ACapacity: integer); 450 | begin 451 | SetLength(Items, ACapacity); 452 | end; 453 | 454 | constructor TArrayRecord.Create(const AValues: array of T); 455 | begin 456 | SetLength(Items, 0); 457 | AddRange(AValues); 458 | end; 459 | 460 | procedure TArrayRecord.Clear; 461 | begin 462 | SetLength(Items, 0); 463 | end; 464 | 465 | 466 | 467 | class operator TArrayRecord.Equal(const L, R: TArrayRecord): boolean; 468 | begin 469 | Result := L.Compare(R); 470 | end; 471 | 472 | 473 | class operator TArrayRecord.NotEqual(const L, R: TArrayRecord): boolean; 474 | begin 475 | Result := not L.Compare(R); 476 | end; 477 | 478 | 479 | 480 | function TArrayRecord.GetCount: integer; 481 | begin 482 | Result := length(Items); 483 | end; 484 | 485 | function TArrayRecord.GetEnumerator: TEnumerator; 486 | begin 487 | Result := TEnumerator.Create(Self); 488 | end; 489 | 490 | procedure TArrayRecord.SetCount(const Value: integer); 491 | begin 492 | SetLength(Items, Value); 493 | end; 494 | 495 | procedure TArrayRecord.SetItemAt(Index: integer; Value: T); 496 | begin 497 | Items[Index] := Value; 498 | end; 499 | 500 | procedure TArrayRecord.SetItems(const Values: array of T); 501 | begin 502 | SetLength(Items, 0); 503 | AddRange(Values); 504 | end; 505 | 506 | function TArrayRecord.GetItemAt(Index: integer): T; 507 | begin 508 | Result := Items[Index]; 509 | end; 510 | 511 | 512 | procedure TArrayRecord.AddRange(const ValuesToInsert: array of T); 513 | begin 514 | TArray.AddRange(Items, ValuesToInsert); 515 | end; 516 | 517 | procedure TArrayRecord.AddRange(const ValuesToInsert: TArrayRecord); 518 | begin 519 | TArray.AddRange(Items, ValuesToInsert.Items); 520 | end; 521 | 522 | 523 | 524 | function TArrayRecord.BinarySearch(const AItem: T; out AFoundIndex: Integer; const AComparer: IComparer; AIndex, 525 | ACount: Integer): Boolean; 526 | begin 527 | Result := TArray.BinarySearch(Items, AItem, AFoundIndex, AComparer, AIndex, ACount); 528 | end; 529 | 530 | function TArrayRecord.BinarySearch(const AItem: T; out AFoundIndex: Integer; const AComparer: IComparer): Boolean; 531 | begin 532 | Result := TArray.BinarySearch(Items, AItem, AFoundIndex, AComparer); 533 | end; 534 | 535 | function TArrayRecord.BinarySearch(const AItem: T; out AFoundIndex: Integer): Boolean; 536 | begin 537 | Result := TArray.BinarySearch(Items, AItem, AFoundIndex); 538 | end; 539 | 540 | 541 | procedure TArrayRecord.Delete(Index: integer); 542 | begin 543 | TArray.Delete(Items, Index); 544 | end; 545 | 546 | 547 | 548 | function TArrayRecord.Remove(const AItem: T): boolean; 549 | var 550 | I: integer; 551 | begin 552 | I := IndexOf(AItem); 553 | if I < 0 then 554 | Result := FALSE 555 | else 556 | begin 557 | Delete(I); 558 | Result := TRUE; 559 | end; 560 | end; 561 | 562 | 563 | function TArrayRecord.AddIfNotContains(const AItem: T): boolean; 564 | begin 565 | Result := Contains(AItem); 566 | if not Result then 567 | Add(AItem); 568 | end; 569 | 570 | 571 | 572 | function TArrayRecord.Find(const Callback: TArrayFindCallback; const StartIndex: integer): integer; 573 | begin 574 | Result := TArray.Find(Items, Callback, StartIndex); 575 | end; 576 | 577 | procedure TArrayRecord.ForEach(const Callback: TArrayForEachCallback); 578 | begin 579 | TArray.ForEach(Items, Callback); 580 | end; 581 | 582 | 583 | 584 | function TArrayRecord.Compare(const ValuesToCompare: TArrayRecord): boolean; 585 | begin 586 | Result := TArray.Compare(Items, ValuesToCompare.Items); 587 | end; 588 | 589 | function TArrayRecord.Compare(const ValuesToCompare: TArrayRecord; const Comparer: IComparer): boolean; 590 | begin 591 | Result := TArray.Compare(Items, ValuesToCompare.Items, Comparer); 592 | end; 593 | 594 | function TArrayRecord.Compare(const ValuesToCompare: array of T): boolean; 595 | begin 596 | Result := TArray.Compare(Items, ValuesToCompare); 597 | end; 598 | 599 | function TArrayRecord.Compare(const ValuesToCompare: array of T; const Comparer: IComparer): boolean; 600 | begin 601 | Result := TArray.Compare(Items, ValuesToCompare, Comparer); 602 | end; 603 | 604 | 605 | 606 | 607 | function TArrayRecord.Contains(Item: T; const Comparer: IComparer): boolean; 608 | begin 609 | Result := TArray.Contains(Items, Item, Comparer); 610 | end; 611 | 612 | 613 | function TArrayRecord.Contains(Item: T): boolean; 614 | begin 615 | Result := TArray.Contains(Items, Item); 616 | end; 617 | 618 | 619 | 620 | 621 | function TArrayRecord.IndexOf(Item: T; const Comparer: IComparer): integer; 622 | begin 623 | Result := TArray.IndexOf(Items, Item, Comparer); 624 | end; 625 | 626 | 627 | function TArrayRecord.IndexOfMax: integer; 628 | begin 629 | Result := TArray.IndexOfMax(Items); 630 | end; 631 | 632 | function TArrayRecord.IndexOfMax(const Comparer: IComparer): integer; 633 | begin 634 | Result := TArray.IndexOfMax(Items, Comparer); 635 | end; 636 | 637 | function TArrayRecord.IndexOfMin: integer; 638 | begin 639 | Result := TArray.IndexOfMin(Items); 640 | end; 641 | 642 | function TArrayRecord.IndexOfMin(const Comparer: IComparer): integer; 643 | begin 644 | Result := TArray.IndexOfMin(Items, Comparer); 645 | end; 646 | 647 | function TArrayRecord.IndexOf(Item: T): integer; 648 | begin 649 | Result := TArray.IndexOf(Items, Item); 650 | end; 651 | 652 | 653 | procedure TArrayRecord.Insert(Index: integer; Value: T); 654 | begin 655 | TArray.Insert(Items, Index, Value); 656 | end; 657 | 658 | 659 | 660 | procedure TArrayRecord.InsertRange(Index: Integer; const ValuesToInsert: TArrayRecord); 661 | begin 662 | TArray.InsertRange(Items, Index, ValuesToInsert.Items); 663 | end; 664 | 665 | procedure TArrayRecord.List(var ValList: TList); 666 | begin 667 | TArray.List(Items, ValList); 668 | end; 669 | 670 | procedure TArrayRecord.InsertRange(Index: Integer; const ValuesToInsert: array of T); 671 | begin 672 | TArray.InsertRange(Items, Index, ValuesToInsert); 673 | end; 674 | 675 | 676 | 677 | function TArrayRecord.Map(const Callback: TArrayMapCallback): TArrayRecord; 678 | begin 679 | Result.Items := TArray.Map(Items, Callback); 680 | end; 681 | 682 | 683 | 684 | function TArrayRecord.Convert(const Callback: TArrayConvert): TArrayRecord; 685 | var 686 | I: Integer; 687 | begin 688 | Result.Clear; 689 | for I := Low(Items) to High(Items) do 690 | Result.Add(Callback(Items[I])); 691 | end; 692 | 693 | 694 | 695 | function TArrayRecord.CopyArray(FromIndex: integer; Count: integer): TArrayRecord; 696 | var 697 | I: Integer; 698 | begin 699 | Result.Clear; 700 | if Count < 0 then 701 | Count := length(Items); 702 | if length(Items) < (FromIndex + Count) then 703 | Count := length(Items) - FromIndex; 704 | if Count > 0 then 705 | begin 706 | SetLength(Result.Items, Count); 707 | for I := 0 to Count-1 do 708 | Result.Items[I] := Items[I + FromIndex]; 709 | end; 710 | end; 711 | 712 | 713 | 714 | procedure TArrayRecord.Sort; 715 | begin 716 | TArray.Sort(Items); 717 | end; 718 | 719 | procedure TArrayRecord.Sort(const AComparer: IComparer); 720 | begin 721 | TArray.Sort(Items, AComparer); 722 | end; 723 | 724 | procedure TArrayRecord.Sort(const AComparer: IComparer; AIndex, ACount: Integer); 725 | begin 726 | TArray.Sort(Items, AComparer, AIndex, ACount); 727 | end; 728 | 729 | function TArrayRecord.Add(const Value: T): integer; 730 | begin 731 | Result := TArray.Add(Items, Value); 732 | end; 733 | 734 | procedure TArrayRecord.Unique; 735 | var 736 | Hash: TDictionary; 737 | I: Integer; 738 | begin 739 | Hash := TDictionary.Create(length(Items)); 740 | try 741 | for I := Low(Items) to High(Items) do 742 | Hash.AddOrSetValue(Items[I], 0); 743 | Items := Hash.Keys.ToArray; 744 | finally 745 | Hash.Free; 746 | end; 747 | end; 748 | 749 | 750 | 751 | {$IFDEF TEST_FUNCTION} 752 | 753 | 754 | type 755 | TTestRecord = record 756 | Name: string; 757 | Age: integer; 758 | constructor Create(AName: string; AAge: integer); 759 | class function NameComparer: IComparer; static; 760 | class function AgeComparer: IComparer; static; 761 | class function ConvertToNames(const Value: TTestRecord): string; static; 762 | class function ConvertToAges(const Value: TTestRecord): integer; static; 763 | end; 764 | 765 | constructor TTestRecord.Create(AName: string; AAge: integer); 766 | begin 767 | Name := AName; 768 | Age := AAge; 769 | end; 770 | 771 | class function TTestRecord.ConvertToNames(const Value: TTestRecord): string; 772 | begin 773 | Result := Value.Name; 774 | end; 775 | 776 | class function TTestRecord.ConvertToAges(const Value: TTestRecord): integer; 777 | begin 778 | Result := Value.Age; 779 | end; 780 | 781 | class function TTestRecord.AgeComparer: IComparer; 782 | begin 783 | Result := TComparer.Construct( 784 | function(const Left, Right: TTestRecord): Integer 785 | begin 786 | Result := TComparer.Default.Compare(Left.Age, Right.Age); 787 | end 788 | ); 789 | end; 790 | 791 | class function TTestRecord.NameComparer: IComparer; 792 | begin 793 | Result := TComparer.Construct( 794 | function(const Left, Right: TTestRecord): Integer 795 | begin 796 | Result := TComparer.Default.Compare(Left.Name, Right.Name); 797 | end 798 | ); 799 | end; 800 | 801 | 802 | procedure Test_TestRecord; 803 | var 804 | List: TArrayRecord; 805 | StrList: TArrayRecord; 806 | I: integer; 807 | begin 808 | // create list 809 | List.Clear; 810 | List.Add( TTestRecord.Create('Jack', 26) ); 811 | List.Add( TTestRecord.Create('Anton', 28) ); 812 | List.Add( TTestRecord.Create('Barbie', 50) ); 813 | List.Add( TTestRecord.Create('Mickey Mouse', 90) ); 814 | 815 | // sort by name 816 | List.Sort( TTestRecord.NameComparer ); 817 | // convert to string array 818 | 819 | StrList := List.Convert(TTestRecord.ConvertToNames); 820 | assert( StrList.Compare(['Anton','Barbie','Jack','Mickey Mouse']) ); 821 | 822 | // convert to integer array 823 | assert( List.Convert(TTestRecord.ConvertToAges).Compare([28,50,26,90]) ); 824 | 825 | // sort by age 826 | List.Sort( TTestRecord.AgeComparer ); 827 | assert( List[0].Name = 'Jack' ); 828 | 829 | // IndexOf Min / Max 830 | assert( List.IndexOfMax(TTestRecord.AgeComparer) = 3 ); 831 | assert( List.IndexOfMin(TTestRecord.AgeComparer) = 0 ); 832 | 833 | I := List.IndexOfMax(TTestRecord.NameComparer); 834 | assert( List[I].Name = 'Mickey Mouse' ); 835 | 836 | I := List.IndexOfMin(TTestRecord.NameComparer); 837 | assert( List[I].Name = 'Anton' ); 838 | 839 | // Unique 840 | List.Add(List[0]); 841 | List.Insert(2, List[1]); 842 | List.Insert(4, List[1]); 843 | List.Unique; 844 | List.Sort(TTestRecord.NameComparer); 845 | StrList := List.Convert(TTestRecord.ConvertToNames); 846 | assert( StrList.Compare(['Anton','Barbie','Jack','Mickey Mouse']) ); 847 | 848 | end; 849 | 850 | 851 | 852 | 853 | function CompareJokerFunction(const Value: string): boolean; 854 | begin 855 | Result := LowerCase(Value) = 'joker'; 856 | end; 857 | 858 | 859 | procedure TestArrayContainer; 860 | const 861 | CWeek: array[1..8] of string = ('Mon','Tues','Wednes','Bug','Thurs','Fri','Satur','Sun'); 862 | var 863 | AStr: TArrayRecord; 864 | AI,AI2: TArrayRecord; 865 | I: Integer; 866 | S: string; 867 | begin 868 | AI := TArrayRecord.Create(0); 869 | assert(AI.Count = 0); 870 | AStr := TArrayRecord.Create(10); 871 | assert((AStr.Count = 10) and (AStr[1] = '')); 872 | 873 | // Create 874 | AI.Create([1,2,3]); 875 | assert( AI.Compare([1,2,3]) ); 876 | 877 | // Add 878 | AI.Clear; 879 | assert( AI.Add(1) = 0 ); 880 | assert( AI.Add(2) = 1 ); 881 | assert( AI.Add(3) = 2 ); 882 | 883 | // IndexOf 884 | assert( AI.IndexOf(1) = 0 ); 885 | assert( AI.IndexOf(2) = 1 ); 886 | assert( AI.IndexOf(5) = -1 ); 887 | 888 | // Contains 889 | assert( AI.Contains(2) = TRUE ); 890 | assert( AI.Contains(5) = FALSE ); 891 | assert( AI.Contains(5, TComparer.Construct( 892 | function(const Left, Right: integer): Integer 893 | begin 894 | Result := (Left + 4) - Right; 895 | end 896 | )) = TRUE ); 897 | 898 | 899 | // Delete 900 | AI.Delete(1); 901 | assert( AI.Contains(2) = FALSE ); 902 | assert( AI.Count = 2 ); 903 | try AI.Delete(2); assert(TRUE); except end; // exception expected 904 | AI.Delete(0); assert( AI.Count = 1 ); 905 | AI.Delete(0); assert( AI.Count = 0 ); 906 | try AI.Delete(0); assert(TRUE); except end; // exception expected 907 | 908 | // Insert 909 | AStr.Clear; 910 | AStr.Insert(0, 'one'); 911 | AStr.Insert(0, 'two'); 912 | assert( AStr.Count = 2 ); 913 | assert( AStr[0] = 'two' ); 914 | assert( AStr[1] = 'one' ); 915 | 916 | AStr.Insert(2, 'three'); 917 | assert( (AStr.Count = 3) and (AStr[2] = 'three') ); 918 | 919 | // AddRange 920 | AI.Clear; 921 | AI.AddRange( TArray.Create(4,5,6) ); 922 | assert( (AI.Count = 3) and (AI[2] = 6) ); 923 | AI.AddRange( TArray.Create(10,11,12) ); 924 | assert( (AI.Count = 6) and (AI[5] = 12) and (AI[0] = 4) ); 925 | 926 | // Compare 927 | AI.Create([1,2,3]); 928 | AI2 := AI; 929 | Assert( AI.Compare([1,2,3]) ); 930 | Assert( AI.Compare(AI.Items) ); 931 | Assert( AI.Compare(AI2) ); 932 | AI2.Add(4); 933 | Assert( not AI.Compare(AI2) ); 934 | 935 | // Equal 936 | AI.Create([1,2,3,4,5,6]); 937 | AI2 := AI; 938 | assert( AI = AI2 ); 939 | AI.AddRange( AI2 ); 940 | assert( (AI.Count = 12) and (AI <> AI2) ); 941 | AI2.InsertRange( AI2.Count, AI2 ); 942 | assert( (AI.Count = AI2.Count) and (AI = AI2) ); 943 | 944 | // InsertRange 945 | AI.Clear; 946 | AI.InsertRange( 0, TArray.Create(4,5,6) ); 947 | assert( (AI.Count = 3) and (AI[2] = 6) ); 948 | AI.InsertRange( 0, [10,11,12]); 949 | assert( (AI.Count = 6) and (AI[5] = 6) and (AI[0] = 10) ); 950 | AI.InsertRange( 3,[21,22]); 951 | assert( (AI.Count = 8) and (AI[7] = 6) and (AI[0] = 10) and (AI[3] = 21) ); 952 | 953 | 954 | // ForEach 955 | AI.Items := TArray.Create(5,4,3,2,1); 956 | AStr.Clear; 957 | AI.ForEach( 958 | procedure(var Value: integer; Index: integer) 959 | begin 960 | Value := Value * 10; 961 | AStr.Add(IntToStr(Value)); 962 | end 963 | ); 964 | // sort 965 | AI.Sort; 966 | AStr.Sort; 967 | assert( AI.Compare([10,20,30,40,50]) ); 968 | assert( AStr.Compare(['10','20','30','40','50']) ); 969 | 970 | 971 | // Find 972 | AI.Clear; 973 | AStr.SetItems(['4','king','joker','7','JOKER','joker','ace','joker']); 974 | I := -1; 975 | repeat 976 | I := AStr.Find(CompareJokerFunction, I+1); 977 | if I >= 0 then AI.Add( I); 978 | until I < 0; 979 | assert( AI.Compare([2,4,5,7]) ); 980 | 981 | 982 | // Map 983 | AI.Clear; 984 | for I := 1 to 50 do AI.Add( I); 985 | AI := AI.Map( 986 | function(var Value: integer; Index: integer): boolean 987 | begin 988 | Result := (Value >= 10) and (Value < 20); 989 | if Result then 990 | Value := Value + 100; 991 | end 992 | ); 993 | assert( AI.Count = 10 ); 994 | assert( AI[1] = 111 ); 995 | 996 | // Map 997 | AStr.SetItems(CWeek); 998 | AStr := AStr.Map( 999 | function(var Value: string; Index: integer): boolean 1000 | begin 1001 | Result := Value <> 'Bug'; 1002 | Value := Value + 'day'; 1003 | end 1004 | ); 1005 | assert( AStr.Contains('Monday') ); 1006 | assert( AStr.Contains('Sunday') ); 1007 | assert( not AStr.Contains('Bugday') ); 1008 | 1009 | // enumerate 1010 | AI.Clear; 1011 | AStr.SetItems(CWeek); 1012 | for S in AStr do 1013 | AI.Add(length(S)); 1014 | assert( AI.Count = AStr.Count ); 1015 | assert( AI.Compare([3,4,6,3,5,3,5,3]) ); 1016 | // check empty enumeration 1017 | AStr.Clear; 1018 | for S in AStr do 1019 | AI.Add(length(S)); 1020 | assert( AI.Compare([3,4,6,3,5,3,5,3]) ); 1021 | 1022 | // Unique 1023 | AI.Unique; 1024 | AI.Sort; 1025 | assert( AI.Compare([3,4,5,6]) ); 1026 | 1027 | // CopyArray 1028 | assert( AI.CopyArray(2).Compare([5,6]) ); 1029 | assert( AI.CopyArray(0,2).Compare([3,4]) ); 1030 | assert( AI.CopyArray(1,2).Compare([4,5]) ); 1031 | 1032 | 1033 | end; 1034 | 1035 | 1036 | 1037 | procedure TestArrayHelper; 1038 | var 1039 | AI: TArray; 1040 | AStr: TArray; 1041 | I: Integer; 1042 | begin 1043 | // Add 1044 | AI := NIL; 1045 | assert( TArray.Add(AI,1) = 0 ); 1046 | assert( TArray.Add(AI,2) = 1 ); 1047 | assert( TArray.Add(AI,3) = 2 ); 1048 | 1049 | // IndexOf 1050 | assert( TArray.IndexOf(AI,1) = 0 ); 1051 | assert( TArray.IndexOf(AI,2) = 1 ); 1052 | assert( TArray.IndexOf(AI,5) = -1 ); 1053 | 1054 | // Contains 1055 | assert( TArray.Contains(AI,2) = TRUE ); 1056 | assert( TArray.Contains(AI,5) = FALSE ); 1057 | assert( TArray.Contains(AI,5, TComparer.Construct( 1058 | function(const Left, Right: integer): Integer 1059 | begin 1060 | Result := Left - (Right + 4); 1061 | end 1062 | )) = FALSE ); 1063 | 1064 | 1065 | // Delete 1066 | TArray.Delete(AI,1); 1067 | assert( TArray.Contains(AI,2) = FALSE ); 1068 | assert( length(AI) = 2 ); 1069 | try TArray.Delete(AI,2); assert(TRUE); except end; // exception expected 1070 | TArray.Delete(AI,0); assert( length(AI) = 1 ); 1071 | TArray.Delete(AI,0); assert( length(AI) = 0 ); 1072 | try TArray.Delete(AI,0); assert(TRUE); except end; // exception expected 1073 | 1074 | // Insert 1075 | AStr := NIL; 1076 | TArray.Insert(AStr, 0, 'one'); 1077 | TArray.Insert(AStr, 0, 'two'); 1078 | assert( length(AStr) = 2 ); 1079 | assert( AStr[0] = 'two' ); 1080 | assert( AStr[1] = 'one' ); 1081 | 1082 | TArray.Insert(AStr, 2, 'three'); 1083 | assert( (length(AStr) = 3) and (AStr[2] = 'three') ); 1084 | 1085 | // AddRange 1086 | AI := NIL; 1087 | TArray.AddRange(AI, TArray.Create(4,5,6)); 1088 | assert( (length(AI) = 3) and (AI[2] = 6) ); 1089 | TArray.AddRange(AI, TArray.Create(10,11,12)); 1090 | assert( (length(AI) = 6) and (AI[5] = 12) and (AI[0] = 4) ); 1091 | 1092 | // InsertRange 1093 | AI := NIL; 1094 | TArray.InsertRange(AI, 0, TArray.Create(4,5,6)); 1095 | assert( (length(AI) = 3) and (AI[2] = 6) ); 1096 | TArray.InsertRange(AI, 0, TArray.Create(10,11,12)); 1097 | assert( (length(AI) = 6) and (AI[5] = 6) and (AI[0] = 10) ); 1098 | TArray.InsertRange(AI, 3, TArray.Create(21,22)); 1099 | assert( (length(AI) = 8) and (AI[7] = 6) and (AI[0] = 10) and (AI[3] = 21) ); 1100 | 1101 | 1102 | // ForEach 1103 | AI := TArray.Create(5,4,3,2,1); 1104 | AStr := NIL; 1105 | TArray.ForEach( AI, 1106 | procedure(var Value: integer; Index: integer) 1107 | begin 1108 | Value := Value * 10; 1109 | TArray.Add(AStr, IntToStr(Value)); 1110 | end 1111 | ); 1112 | TArray.Sort(AI); 1113 | TArray.Sort(AStr); 1114 | assert( TArray.Compare(AI, TArray.Create(10,20,30,40,50)) ); 1115 | assert( TArray.Compare(AStr, TArray.Create('10','20','30','40','50')) ); 1116 | 1117 | 1118 | // Find 1119 | AI := NIL; 1120 | AStr := TArray.Create('4','king','joker','7','JOKER','joker','ace','joker'); 1121 | I := -1; 1122 | repeat 1123 | I := TArray.Find(AStr, CompareJokerFunction, I+1); 1124 | if I >= 0 then TArray.Add(AI, I); 1125 | until I < 0; 1126 | assert( TArray.Compare(AI, TArray.Create(2,4,5,7)) ); 1127 | 1128 | 1129 | // Map 1130 | AI := NIL; 1131 | for I := 1 to 50 do TArray.Add(AI, I); 1132 | AI := TArray.Map(AI, 1133 | function(var Value: integer; Index: integer): boolean 1134 | begin 1135 | Result := (Value >= 10) and (Value < 20); 1136 | if Result then 1137 | Value := Value + 100; 1138 | end 1139 | ); 1140 | assert( length(AI) = 10 ); 1141 | assert( AI[1] = 111 ); 1142 | 1143 | // Map 1144 | AStr := TArray.Create('Mon','Tues','Wednes','Thurs','Fri','Satur','Sun'); 1145 | AStr := TArray.Map( AStr, 1146 | function(var Value: string; Index: integer): boolean 1147 | begin 1148 | Result := TRUE; 1149 | Value := Value + 'day'; 1150 | end 1151 | ); 1152 | assert( TArray.Contains(AStr, 'Monday') ); 1153 | assert( TArray.Contains(AStr, 'Sunday') ); 1154 | 1155 | end; 1156 | 1157 | class procedure TArrayHelper.Test_All_Helper_Functions; 1158 | begin 1159 | TestArrayHelper; 1160 | TestArrayContainer; 1161 | Test_TestRecord; 1162 | end; 1163 | 1164 | {$ENDIF TEST_FUNCTION} 1165 | 1166 | 1167 | 1168 | end. 1169 | 1170 | 1171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArrayHelper 2 | 3 | Delphi class helper for TArray. Class functions Add, Delete, IndexOf, Map and more, including examples and test. 4 | 5 | Dynamic arrays are smart because its memory usage is handled by the memory 6 | manager. But the function libraries are lean and differs from object based. 7 | Based on TArray class, that gives Sort and Binary search, this unit will 8 | extend TArray with functions available for TList or TStrings. 9 | 10 | The next level is TArrayRecord record type. It wraps a record around 11 | the dynamic array. This give us the ability to use dynamic arrays like 12 | objects with out the pain to organize the final Free call. 13 | 14 | There are test functions with examples included. 15 | 16 | 17 | ## Usage 18 | 19 | Add ArrayHelpher to your 'uses' section. 20 | 21 | use TArray like this 22 | 23 | var 24 | A: TArray; 25 | begin 26 | A := NIL; 27 | TArray.Insert( A, 0, 'one' ); 28 | TArray.Add( A, 'two' ); 29 | if TArray.Contains( A, 'one' ) then ... 30 | 31 | use TArrayRecord like this 32 | 33 | var 34 | A: TArrayRecord; 35 | begin 36 | A.SetValues(['a','b','c']); 37 | A.Add('d'); 38 | assert( A.Count = 4 ); // same as length(A.Items); 39 | assert( A[1] = 'b' ); 40 | assert( A.IndexOf('a') = 0 ); 41 | for S in A do 42 | .. 43 | --------------------------------------------------------------------------------