forked from grijjy/GrijjyFoundation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Grijjy.Collections.pas
2013 lines (1672 loc) · 55.1 KB
/
Grijjy.Collections.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
unit Grijjy.Collections;
{$INCLUDE 'Grijjy.inc'}
interface
uses
System.Generics.Defaults,
System.Generics.Collections;
type
{ Various utilities that operate on generic dynamic arrays. Mostly used
internally by various generic collections. }
TgoArray<T> = class // static
public
{ Moves items within an array.
Parameters:
AArray: the array
AFromIndex: the source index into AArray
AToIndex: the destination index into AArray
ACount: the number of elements to move.
You should use this utility instead of System.Move since it correctly
handles elements with [weak] references.
@bold(Note): no range checking is performed on the arguments. }
class procedure Move(var AArray: TArray<T>; const AFromIndex, AToIndex,
ACount: Integer); overload; static;
{ Moves items from one array to another.
Parameters:
AFromArray: the source array
AFromIndex: the source index into AFromArray
AToArray: the destination array
AToIndex: the destination index into AToArray
ACount: the number of elements to move.
You should use this utility instead of System.Move since it correctly
handles elements with [weak] references.
@bold(Note): no range checking is performed on the arguments. }
class procedure Move(const AFromArray: TArray<T>; const AFromIndex: Integer;
var AToArray: TArray<T>; const AToIndex, ACount: Integer); overload; static;
{ Finalizes an element in an array.
Parameters:
AArray: the array containing the element to finalize.
AIndex: the index of the element to finalize.
You should call this utility to mark an element in an array as "unused".
This prevents memory problems when the array contains elements that are
reference counted or contain [weak] references. In those cases, the
element will be set to all zero's. If the array contains "regular"
elements, then this method does nothing.
@bold(Note): no range checking is performed on the arguments. }
class procedure Finalize(var AArray: TArray<T>;
const AIndex: Integer); overload; static; inline;
{ Finalizes a range ofelements in an array.
Parameters:
AArray: the array containing the elements to finalize.
AIndex: the index of the first element to finalize.
ACount: the number of elements to finalize.
You should call this utility to mark an element in an array as "unused".
This prevents memory problems when the array contains elements that are
reference counted or contain [weak] references. In those cases, the
element will be set to all zero's. If the array contains "regular"
elements, then this method does nothing.
@bold(Note): no range checking is performed on the arguments. }
class procedure Finalize(var AArray: TArray<T>; const AIndex,
ACount: Integer); overload; static; inline;
end;
type
{ Generic read-only set. Provides a read-only view of a TgoSet<T> }
TgoReadOnlySet<T> = class(TEnumerable<T>)
{$REGION 'Internal Declarations'}
private const
EMPTY_HASH = -1;
private type
TItem = record
HashCode: Integer;
Item: T;
end;
private type
TEnumerator = class(TEnumerator<T>)
{$REGION 'Internal Declarations'}
private
FItems: TArray<TItem>;
FIndex: Integer;
FHigh: Integer;
protected
{ TEnumerator<T> }
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const AItems: TArray<TItem>);
end;
private
FItems: TArray<TItem>;
FCount: Integer;
FComparer: IEqualityComparer<T>;
{$ENDREGION 'Internal Declarations'}
public
{ TEnumerable<T> }
{ Copies the items in the set to a dynamic array }
function ToArray: TArray<T>; override; final;
{ Allow <tt>for..in</tt> enumeration of the items in the set. }
function DoGetEnumerator: TEnumerator<T>; override;
public
{ Creates a read-only set using a default comparer. }
constructor Create; overload;
{ Creates a read-only set using a custom comparer.
Parameters:
AComparer: the comparer to use to check for item equality.
Pass nil to use the default comparer. }
constructor Create(const AComparer: IEqualityComparer<T>); overload;
{ Checks if the set contains a given item.
This is an O(1) operation that uses the set's comparer to check for
equality.
Parameters:
AItem: the item to check.
Returns:
True if the set contains AItem, False if not. }
function Contains(const AItem: T): Boolean;
{ The number of items in the set }
property Count: Integer read FCount;
end;
type
{ A generic unordered set of values. Is similar to TList<T> in that it
contains a list of items, but the items are not in any specific order. It
uses a hash table to quickly lookup items in the set.
This class is also similar to TDictionary<TKey, TValue>, but with only
keys and no values.
This class is typically used when you need to quickly find items in a
collection, but don't need any specific ordering.
See also TgoObjectSet<T> for a set that owns its items }
TgoSet<T> = class(TgoReadOnlySet<T>)
{$REGION 'Internal Declarations'}
private
FGrowThreshold: Integer;
private
procedure Resize(ANewSize: Integer);
protected
procedure DoRemove(AIndex: Integer; const AMask: Integer); virtual;
{$ENDREGION 'Internal Declarations'}
public
{ Adds an item to the set, raising an exception if the set already contains
the item.
Parameters:
AItem: the item to add. }
procedure Add(const AItem: T);
{ Adds an item to the set if it doesn't exist yet. If the set already
contains the item, then nothing happens.
Parameters:
AItem: the item to add. }
procedure AddOrSet(const AItem: T);
{ Removes an item from the set. Does nothing if the set does not contain the
item.
Parameters:
AItem: the item to remove. }
procedure Remove(const AItem: T);
{ Clears the set. }
procedure Clear; virtual;
end;
type
{ A specialized TgoSet<T> that can only hold objects.
This set owns the items in it, meaning it will automatically free any item
that is removed, unless Extract is used. }
TgoObjectSet<T: class> = class(TgoSet<T>)
{$REGION 'Internal Declarations'}
protected
procedure DoRemove(AIndex: Integer; const AMask: Integer); override;
{$ENDREGION 'Internal Declarations'}
public
{ The destructor will free all items in the set }
destructor Destroy; override;
{ Clears the set and frees the items in it. }
procedure Clear; override;
{ Removes and returns an item in the set, @bold(without) freeing it.
Parameters:
AItem: the item to extract and remove.
Returns:
AItem if the item is in the set, or Default(T) otherwise. }
function Extract(const AItem: T): T;
end;
type
{ A ring buffer (aka "circular buffer"). This is a fixed-size buffer as if it
were connected end-to-end. Lends itself for buffering data streams.
This is a generic implementation that you can use to buffer any value type
(such as integers, bytes, floats and records). It does @bold(not) work with
reference types (such as strings, objects and interfaces).
All "Count"s used in this class refer the a number of elements (of type T),
and @bold(not) to a number of bytes (unless T is an 8-bit value). }
TgoRingBuffer<T: record> = class
{$REGION 'Internal Declarations'}
private
FBuffer: TArray<T>;
FCapacity: Integer;
FCount: Integer;
FReadIndex: Integer;
FWriteIndex: Integer;
function GetAvailable: Integer;
private
function DoWrite(const AData; const ACount: Integer): Integer;
function DoTryWrite(const AData; const ACount: Integer): Boolean;
function DoRead(var AData; const ACount: Integer): Integer;
function DoTryRead(var AData; const ACount: Integer): Boolean;
{$ENDREGION 'Internal Declarations'}
public
{ Creates a ring buffer of a given size.
Parameters:
ACapacity: the number of elements (of type T) that the buffer will hold. }
constructor Create(const ACapacity: Integer);
{ Writes a data array to the buffer.
Parameters:
AData: the data to write to the buffer.
Returns:
The number of elements written to the buffer. This may be less than the
length of AData in case the buffer has become full. }
function Write(const AData: TArray<T>): Integer; overload;
function Write(const AData: array of T): Integer; overload;
{ Writes a segment of a data array to the buffer.
Parameters:
AData: array containing the data to write to the buffer.
AIndex: start index into AData.
ACount: number of elements in AData to write to the buffer, starting at
AIndex.
Returns:
The number of elements written to the buffer. This may be less than
ACount in case the buffer has become full.
AIndex and ACount must point to a valid segment in the array. }
function Write(const AData: TArray<T>; const AIndex, ACount: Integer): Integer; overload;
function Write(const AData: array of T; const AIndex, ACount: Integer): Integer; overload;
{ Tries to write a data array to the buffer. Either the entire operation
will succeed or fail. Unlike Write, no data will be written if this
operation would lead to an overflow.
Parameters:
AData: the data to write to the buffer.
Returns:
True if the data was successfully written, or False in case the buffer
would overflow if the data would be written. In that case, no data is
written to the buffer at all. }
function TryWrite(const AData: TArray<T>): Boolean; overload;
function TryWrite(const AData: array of T): Boolean; overload;
{ Tries to write a segment of a data array to the buffer. Either the entire
operation will succeed or fail. Unlike Write, no data will be written if
this operation would lead to an overflow.
Parameters:
AData: array containing the data to write to the buffer.
AIndex: start index into AData.
ACount: number of elements in AData to write to the buffer, starting at
AIndex.
Returns:
True if the data was successfully written, or False in case the buffer
would overflow if the data would be written. In that case, no data is
written to the buffer at all.
AIndex and ACount must point to a valid segment in the array. }
function TryWrite(const AData: TArray<T>; const AIndex, ACount: Integer): Boolean; overload;
function TryWrite(const AData: array of T; const AIndex, ACount: Integer): Boolean; overload;
{ Reads to a data array from the buffer.
Parameters:
AData: the data array that will be filled with data read from the
buffer. It will try to read Length(AData) elements to fill the entire
array.
Returns:
The number of elements read from buffer. This may be less than the
length of AData in case the buffer did not have enough data available. }
function Read(var AData: TArray<T>): Integer; overload;
function Read(var AData: array of T): Integer; overload;
{ Reads to a segment of a data array from the buffer.
Parameters:
AData: the data array that will be filled with data read from the
buffer.
AIndex: start index into AData.
ACount: the number of elements to read into AData, starting at AIndex.
Returns:
The number of elements read from buffer. This may be less than ACount
in case the buffer did not have enough data available.
AIndex and ACount must point to a valid segment in the array. }
function Read(var AData: TArray<T>; const AIndex, ACount: Integer): Integer; overload;
function Read(var AData: array of T; const AIndex, ACount: Integer): Integer; overload;
{ Tries to read to a data array from the buffer. Either the entire operation
will succeed or fail. Unlike Read, no data will be read if the buffer does
not have enough data available.
Parameters:
AData: the data array that will be filled with data read from the
buffer. It will try to read Length(AData) elements to fill the entire
array.
Returns:
True if the data was successfully read, or False if the buffer does not
have enough data available to read the requested amount. In that case,
no data is read from the buffer at all. }
function TryRead(var AData: TArray<T>): Boolean; overload;
function TryRead(var AData: array of T): Boolean; overload;
{ Tries to read to a segment of a data array from the buffer. Either the
entire operation will succeed or fail. Unlike Read, no data will be read
if the buffer does not have enough data available.
Parameters:
AData: the data array that will be filled with data read from the
buffer.
AIndex: start index into AData.
ACount: the number of elements to read into AData, starting at AIndex.
Returns:
True if the data was successfully read, or False if the buffer does not
have enough data available to read the requested amount. In that case,
no data is read from the buffer at all.
AIndex and ACount must point to a valid segment in the array. }
function TryRead(var AData: TArray<T>; const AIndex, ACount: Integer): Boolean; overload;
function TryRead(var AData: array of T; const AIndex, ACount: Integer): Boolean; overload;
{ The capacity of the ring buffer (in number of elements), as passed to the
constructor. }
property Capacity: Integer read FCapacity;
{ The number of elements currently in the buffer (available for reading). }
property Count: Integer read FCount;
{ The number of elements available for writing (= Capacity - Count). }
property Available: Integer read GetAvailable;
end;
type
{ Generic record used to define a pointer to a value type. For example, you
can declare:
<source>
var
FooPtr: TgoPtr<TFoo>.P;
</source>
which is internally equivalent to:
<source>
var
FooPtr: ^TFoo;
</source>
However, this generic record makes it easier to work with value-type
collections such as TgoValueList and TgoValueDictionary. Without this
record type, you would have to write something like:
<source>
var
FooPtr: TgoValueDictionary<Integer, TFoo>.P;
</source> }
TgoPtr<T: record> = record
public type
P = ^T;
end;
type
{ A light-weight list of value types (primitive types and records). Differs
from TList<T> in the following regards:
* You cannot store reference types (objects, interfaces, strings or dynamic
arrays) in the list.
* It is more light-weight since it doesn't support noticications,
comparers and only checks bounds with assertions (which can be turned off).
* When requesting an item (using Items, First, Last etc.), it returns a
@bold(Pointer) to the type instead of the actual type. This can be both
more efficient, and it allows you to directly modify the item in the list.
* It grows in increments of 16 instead of doubling its size.
The pointer is of type TgoPtr<T>.P. It may be easier to declare your
own pointer type as in <code>PFoo = TgoPtr<TFoo>.P</code>.
Note that you should not cache these pointers for long-term use as they
become invalid when you modify the list (add or remove items). }
TgoValueList<T: record> = class
public type
{ The pointer type for referencing items in this list. }
P = TgoPtr<T>.P;
{$REGION 'Internal Declarations'}
private type
TEnumerator = class(TEnumerator<P>)
{$REGION 'Internal Declarations'}
private
FItems: TArray<T>;
FHigh: Integer;
FIndex: Integer;
protected
{ TEnumerator<P> }
function DoGetCurrent: P; override;
function DoMoveNext: Boolean; override;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const AItems: TArray<T>; const ACount: Integer);
end;
private
FItems: TArray<T>;
FCount: Integer;
function GetItem(const AIndex: Integer): P;
procedure SetCount(const Value: Integer);
{$ENDREGION 'Internal Declarations'}
public
{ Creates a new list. The list is initially emtpy and will grow in
increments of 16 items. }
constructor Create;
{ Clears the list. }
procedure Clear;
{ Adds an item to the end of the list.
Parameters:
AValue: the item to add.
Returns:
The index of the added item }
function Add(const AValue: T): Integer;
{ Inserts an item at a given index into the list.
Parameters:
AIndex: the location to insert the item. An assertion is used when the
index is out of bounds.
AValue: the item to add. }
procedure Insert(const AIndex: Integer; const AValue: T);
{ Deletes an item from the list.
Parameters:
AIndex: the index of the item to delete. An assertion is used when the
index is out of bounds.}
procedure Delete(const AIndex: Integer);
{ Deletes a range of items from the list.
Parameters:
AIndex: the index of the first item to delete
ACount: the number of items to delete }
procedure DeleteRange(const AIndex, ACount: Integer);
{ Returns a pointer to the first item in the list.
An assertion is used when the list is empty.
@bold(Note): You should not cache the returned pointer for long-term use
as it is only valid as long as you don't modify the list. }
function First: P;
{ Returns a pointer to the last item in the list.
An assertion is used when the list is empty.
@bold(Note): You should not cache the returned pointer for long-term use
as it is only valid as long as you don't modify the list. }
function Last: P;
{ Allows for <code>for..in</code> enumeration of the list.
Since the enumerator returns pointers to the items, you use it like this:
<source>
var
Items: TgoValueList<TFoo>;
Item: TgoPtr<TFoo>.P;
begin
// Initialize Items
for Item in Items do...
end;
</source> }
function GetEnumerator: TEnumerator;
{ The number of items in the list }
property Count: Integer read FCount write SetCount;
{ The items in the list. Returns a pointer to the requested item.
Parameters:
Index: the index of the requested item. An assertion is used when the
index is out of bounds.
@bold(Note): You should not cache the returned pointer for long-term use
as it is only valid as long as you don't modify the list. }
property Items[const AIndex: Integer]: P read GetItem; default;
end;
type
{ A light-weight dictionary where the values are of value types (primitive
types and records). Differs from TDictionary<TKey, TValue> in the
following regards:
* The values in the dictionary cannot be reference types (objects,
interfaces, strings or dynamic arrays).
* It is more light-weight since it doesn't support noticications and has
better optimized code.
* When requesting a value (using TryGetValue or Items), it returns a
@bold(Pointer) to the type instead of the actual type. This can be both
more efficient, and it allows you to directly modify the value in the
dictionary.
The pointer is of type TgoPtr<TValue>.P. It may be easier to declare your
own pointer type as in <code>PFoo = TgoPtr<TFoo>.P</code>.
Note that you should not cache these pointers for long-term use as they
become invalid when you modify the dictionary (add or remove items). }
TgoValueDictionary<TKey; TValue: record> = class
public type
{ The pointer type for referencing values in this dictionary. }
PValue = TgoPtr<TValue>.P;
{$REGION 'Internal Declarations'}
private const
EMPTY_HASH = -1;
private type
TEnumerator = class
{$REGION 'Internal Declarations'}
private type
PValue = TgoPtr<TValue>.P;
private
FDictionary: TgoValueDictionary<TKey, TValue>;
FIndex: Integer;
FHigh: Integer;
function GetCurrent: TPair<TKey, PValue>;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const ADictionary: TgoValueDictionary<TKey, TValue>);
function MoveNext: Boolean;
property Current: TPair<TKey, PValue> read GetCurrent;
end;
private type
TKeyEnumerator = class
{$REGION 'Internal Declarations'}
private
FDictionary: TgoValueDictionary<TKey, TValue>;
FIndex: Integer;
FHigh: Integer;
function GetCurrent: TKey;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const ADictionary: TgoValueDictionary<TKey, TValue>);
function MoveNext: Boolean;
property Current: TKey read GetCurrent;
end;
private type
TKeyCollection = class
{$REGION 'Internal Declarations'}
private
[weak] FDictionary: TgoValueDictionary<TKey, TValue>;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const ADictionary: TgoValueDictionary<TKey, TValue>);
function GetEnumerator: TKeyEnumerator;
function ToArray: TArray<TKey>;
end;
private type
TValueEnumerator = class
{$REGION 'Internal Declarations'}
public type
PValue = TgoPtr<TValue>.P;
private
FDictionary: TgoValueDictionary<TKey, TValue>;
FIndex: Integer;
FHigh: Integer;
function GetCurrent: PValue;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const ADictionary: TgoValueDictionary<TKey, TValue>);
function MoveNext: Boolean;
property Current: PValue read GetCurrent;
end;
private type
TValueCollection = class
{$REGION 'Internal Declarations'}
private
[weak] FDictionary: TgoValueDictionary<TKey, TValue>;
{$ENDREGION 'Internal Declarations'}
public
constructor Create(const ADictionary: TgoValueDictionary<TKey, TValue>);
function GetEnumerator: TValueEnumerator;
function ToArray: TArray<TValue>;
end;
private type
TItem = record
HashCode: Integer;
Key: TKey;
Value: TValue;
end;
private
FItems: TArray<TItem>;
FCount: Integer;
FComparer: IEqualityComparer<TKey>;
FGrowThreshold: Integer;
FKeys: TKeyCollection;
FValues: TValueCollection;
private
function GetItem(const AKey: TKey): PValue;
function GetKeys: TKeyCollection;
function GetValues: TValueCollection;
private
procedure Resize(ANewSize: Integer);
procedure DoRemove(AIndex, AMask: Integer);
{$ENDREGION 'Internal Declarations'}
public
{ Creates a dictionary using a default comparer}
constructor Create; overload;
{ Creates a dictionary using a custom comparer.
Parameters:
AComparer: the comparer to use to check for item equality.
Pass nil to use the default comparer. }
constructor Create(const AComparer: IEqualityComparer<TKey>); overload;
{ Destructor }
destructor Destroy; override;
{ Add a value to the dictionary for a specified key.
Parameters:
AKey: the key
AValue: the value to add for the key
Raises:
EListError if the dictionary already contains the given Key.
Use AddOrSetValue to overwrite an existing key with a new value. }
procedure Add(const AKey: TKey; const AValue: TValue);
{ Adds or sets a value in the dictionary for a specified key.
Parameters:
AKey: the key
AValue: the value to set or replace for the key
If the dictionary already contains a value for the given key, then that
value is replaced. Otherwise, the value is added to the dictionary. }
procedure AddOrSetValue(const AKey: TKey; const AValue: TValue);
{ Removes the value for a specified key from the dictionary.
Parameters:
AKey: the key to remove
Returns:
True if the key was removed, or False if the dictionary does not contain
the given key. }
function Remove(const AKey: TKey): Boolean;
{ Clears the dictionary }
procedure Clear;
{ Tries to get a value for the given key.
Parameters:
AKey: the key to search for
AValue: is set to a pointer to the value associated with the given key,
or nil if the dictionary does not contain the key.
Returns:
True if the dictionary contains the given key. In that case, AValue is
set to a pointer to the value associated with the key.
False if the dictionary does not contain the given key. In that case,
AValue is set to nil.
@bold(Note): You should not cache the pointer returned in AValue for
long-term use as it is only valid as long as you don't modify the
dictionary. }
function TryGetValue(const AKey: TKey; out AValue: PValue): Boolean;
{ Checks if the dictionary contains a given key.
Parameters:
AKey: the key to check.
Returns:
True if the dictionary contains AKey, False if not. }
function ContainsKey(const AKey: TKey): Boolean;
{ Allows for <code>for..in</code> enumeration of pairs in the dictionary.
The value in each pair is a pointer to the actual value, so you would use
the enumerator like this:
<source>
var
Dictionary: TgrValueDictionary<String, TFoo>;
Pair: TPair<String, TgrPtr<TFoo>.P>;
begin
// Initialize Dictionary
for Pair in Dictionary do...
end;
</source> }
function GetEnumerator: TEnumerator;
{ Copies the pairs in the dictionary to a dynamic array }
function ToArray: TArray<TPair<TKey, TValue>>;
{ Returns the value for a given key, raising an exception if the dictionary
does not contain the key.
Parameters:
AKey: the key to check.
Returns:
A pointer to the value for the given key.
Raises:
EListError if the dictionary doesn't contain AKey
@bold(Note): You should not cache the returned pointer for long-term use
as it is only valid as long as you don't modify the dictionary. }
property Items[const AKey: TKey]: PValue read GetItem; default;
{ All keys in the dictionary. You can enumerate all keys like this:
<source>
var
Dictionary: TgrValueDictionary<String, TFoo>;
Key: String;
begin
// Initialize Dictionary
for Key in Dictionary.Keys do...
end;
</source> }
property Keys: TKeyCollection read GetKeys;
{ All values in the dictionary. You can enumerate @bold(pointers) to all
values like this:
<source>
var
Dictionary: TgrValueDictionary<String, TFoo>;
Value: TgrPtr<TFoo>.P;
begin
// Initialize Dictionary
for Value in Dictionary.Values do...
end;
</source> }
property Values: TValueCollection read GetValues;
{ The number of items in the dictionary }
property Count: Integer read FCount;
end;
implementation
uses
System.Math,
System.SysUtils,
System.RTLConsts;
{ TgoArray<T> }
class procedure TgoArray<T>.Finalize(var AArray: TArray<T>; const AIndex,
ACount: Integer);
begin
{$IF Defined(WEAKREF)}
if System.HasWeakRef(T) then
begin
System.Finalize(AArray[AIndex], ACount);
FillChar(AArray[AIndex], ACount * SizeOf(T), 0);
end
else
{$ENDIF}
if IsManagedType(T) then
FillChar(AArray[AIndex], ACount * SizeOf(T), 0);
end;
class procedure TgoArray<T>.Finalize(var AArray: TArray<T>;
const AIndex: Integer);
begin
{$IF Defined(WEAKREF)}
if System.HasWeakRef(T) then
begin
System.Finalize(AArray[AIndex], 1);
FillChar(AArray[AIndex], SizeOf(T), 0);
end
else
{$ENDIF}
if IsManagedType(T) then
FillChar(AArray[AIndex], SizeOf(T), 0);
end;
class procedure TgoArray<T>.Move(const AFromArray: TArray<T>;
const AFromIndex: Integer; var AToArray: TArray<T>; const AToIndex,
ACount: Integer);
{$IFDEF WEAKREF}
var
I: Integer;
{$ENDIF}
begin
{$IFDEF WEAKREF}
if System.HasWeakRef(T) then
begin
for I := 0 to ACount - 1 do
AToArray[AToIndex + I] := AFromArray[AFromIndex + I];
end
else
{$ENDIF}
System.Move(AFromArray[AFromIndex], AToArray[AToIndex], ACount * SizeOf(T));
end;
class procedure TgoArray<T>.Move(var AArray: TArray<T>; const AFromIndex,
AToIndex, ACount: Integer);
{$IFDEF WEAKREF}
var
I: Integer;
{$ENDIF}
begin
{$IFDEF WEAKREF}
if System.HasWeakRef(T) then
begin
if (ACount > 0) then
begin
if (AFromIndex < AToIndex) then
begin
for I := ACount - 1 downto 0 do
AArray[AToIndex + I] := AArray[AFromIndex + I]
end
else if (AFromIndex > AToIndex) then
begin
for I := 0 to ACount - 1 do
AArray[AToIndex + I] := AArray[AFromIndex + I];
end;
end;
end
else
{$ENDIF}
System.Move(AArray[AFromIndex], AArray[AToIndex], ACount * SizeOf(T));
end;
{ TgoReadOnlySet<T> }
function TgoReadOnlySet<T>.Contains(const AItem: T): Boolean;
var
Mask, Index, HashCode, HC: Integer;
begin
if (FCount = 0) then
Exit(False);
HashCode := FComparer.GetHashCode(AItem) and $7FFFFFFF;
Mask := Length(FItems) - 1;
Index := HashCode and Mask;
while True do
begin
HC := FItems[Index].HashCode;
if (HC = EMPTY_HASH) then
Exit(False);
if (HC = HashCode) and FComparer.Equals(FItems[Index].Item, AItem) then
Exit(True);
Index := (Index + 1) and Mask;
end;
Result := False;
end;
constructor TgoReadOnlySet<T>.Create;
begin
Create(nil);
end;
constructor TgoReadOnlySet<T>.Create(const AComparer: IEqualityComparer<T>);
begin
inherited Create;
FComparer := AComparer;
if (FComparer = nil) then
FComparer := TEqualityComparer<T>.Default;
end;
function TgoReadOnlySet<T>.DoGetEnumerator: TEnumerator<T>;
begin
Result := TEnumerator.Create(FItems);
end;
function TgoReadOnlySet<T>.ToArray: TArray<T>;
var
I, Count: Integer;
begin
SetLength(Result, FCount);
Count := 0;
for I := 0 to Length(FItems) - 1 do
begin
if (FItems[I].HashCode <> EMPTY_HASH) then
begin
Result[Count] := FItems[I].Item;
Inc(Count);
end;
end;
Assert(Count = FCount);
end;
{ TgoReadOnlySet<T>.TEnumerator }
constructor TgoReadOnlySet<T>.TEnumerator.Create(
const AItems: TArray<TItem>);
begin
inherited Create;
FItems := AItems;
FHigh := Length(AItems) - 1;
FIndex := -1;
end;
function TgoReadOnlySet<T>.TEnumerator.DoGetCurrent: T;
begin
Result := FItems[FIndex].Item;
end;
function TgoReadOnlySet<T>.TEnumerator.DoMoveNext: Boolean;
begin
while (FIndex < FHigh) do
begin
Inc(FIndex);
if (FItems[FIndex].HashCode <> EMPTY_HASH) then
Exit(True);
end;
Result := False;
end;
{ TgoSet<T> }
procedure TgoSet<T>.Add(const AItem: T);
var
Mask, Index, HashCode, HC: Integer;
begin
if (FCount >= FGrowThreshold) then
Resize(Length(FItems) * 2);
HashCode := FComparer.GetHashCode(AItem) and $7FFFFFFF;
Mask := Length(FItems) - 1;
Index := HashCode and Mask;
while True do
begin
HC := FItems[Index].HashCode;
if (HC = EMPTY_HASH) then
Break;
if (HC = HashCode) and FComparer.Equals(FItems[Index].Item, AItem) then
raise EListError.CreateRes(@SGenericDuplicateItem);
Index := (Index + 1) and Mask;
end;
FItems[Index].HashCode := HashCode;
FItems[Index].Item := AItem;