⬆️ ⬇️

Indexing is global and not very

Immediately make a reservation, the article has nothing to do with site indexing, etc. It will be a question of things much simpler, but, nevertheless, necessary. Sometimes it is necessary to index entities, so that the indices are unique within the program and compactly packed in the gap [0..N]. Moreover, there is no wish to start separate mechanisms for this.



An example would be such a task:



Everyone, I think, knows that class var in Delphi is nothing more than an ordinary global variable, common to a class and all its descendants. And sometimes it is necessary for descendants to use their own, for example, to count class instances. I know at least one solution to this problem, but this is a hack. In addition, it requires additional actions from the user - memory allocation in the initialization block and, in a good way, its release in finalization.

')

But you can make it easier - get a global (class var) array, and make each descendant refer to its own cell. The only problem is that for this you need to index the descendants, and to do this automatically.



In my case, the task was somewhat different. I wanted to add a universal opportunity for classes to include / exclude themselves in sets and test themselves for O (1) membership. That is, to add the field "signatures", giving such an opportunity, regardless of the number of supposed sets, and not to bind classes by any common ancestor. In any case, at a certain stage, I came to the problem of indexing these very sets.



After a little wandering around the Internet, I did not find a ready-made solution. Actually, I did not see such a task at all. In the meantime, I think this would have many uses.



In general, the task is simple. Perhaps this was the reason for the lack of talk on this topic. In the most primitive form, the code takes only a few lines:



type TIndexator<TIdent> = class private var FIndexTable: TDictionary<TIdent, Integer>; public constructor Create; destructor Destroy; override; function GetIndex(Ident: TIdent): Integer; end; function TIndexator<TIdent>.GetIndex(Ident: TIdent): Integer; begin if not FIndexTable.TryGetValue(Ident, Result) then begin Result := FIndexTable.Count; FIndexTable.Add(Ident, Result); end; end; 


But it seemed to me not enough. In such an implementation, it is necessary to create additional variables, usually global ones, and to monitor their initialization. Also, some flexibility is lacking. In general, I decided to improve the approach a bit, and the result was this:



 type TGlobalIndexator<TIdent> = class private type TIdentTable = TList<TIdent>; TIndexTable = TDictionary<TIdent, Integer>; PClientField = ^TClientField; TClientField = record IndexNames: TIdentTable; IndexTable: TIndexTable; end; TClientTable = TDictionary<Pointer, PClientField>; strict private class var FClientTable: TClientTable; class constructor InitClass; class function GetField(Client: Pointer): PClientField; public class function GetIndex(Ident: TIdent): Integer; overload; class function GetIndex(Client: Pointer; Ident: TIdent): Integer; overload; class function GetIdent(Index: Integer): TIdent; overload; class function GetIdent(Client: Pointer; Index: Integer): TIdent; overload; end; class constructor TGlobalIndexator<TIdent>.InitClass; begin FClientTable := TClientTable.Create; end; class function TGlobalIndexator<TIdent>.GetField( Client: Pointer): PClientField; begin if not FClientTable.TryGetValue(Client, Result) then begin New(Result); Result.IndexNames := TIdentTable.Create; Result.IndexTable := TIndexTable.Create; FClientTable.Add(Client, Result); end; end; class function TGlobalIndexator<TIdent>.GetIndex(Client: Pointer; Ident: TIdent): Integer; var Field: PClientField; begin //Writeln('GetIndex(', Client.ClassName, ', , Ident, );'); Field := GetField(Client); if not Field.IndexTable.TryGetValue(Ident, Result) then begin Result := Field.IndexNames.Count; Field.IndexNames.Add(Ident); Field.IndexTable.Add(Ident, Result); end; end; class function TGlobalIndexator<TIdent>.GetIndex(Ident: TIdent): Integer; begin Result := GetIndex(Pointer(Self), Ident); end; class function TGlobalIndexator<TIdent>.GetIdent(Client: Pointer; Index: Integer): TIdent; var Field: PClientField; begin Field := GetField(Client); if Index < Field.IndexNames.Count then Result := Field.IndexNames[Index] else raise Exception.CreateFmt('Index %d is not registered', [Index]); end; class function TGlobalIndexator<TIdent>.GetIdent(Index: Integer): TIdent; begin Result := GetIdent(Pointer(Self), Index); end; 


The code is still not complicated. As you can see, there are no index removal procedures. This is done specifically to avoid many problems associated with the use of the "old" index.



You can apply this class in two ways: In simple cases, you can simply specify a new descendant of the class. It is not necessary to redefine anything, only the fact of creating a new class is important.



 type TMyStringIndexator = class(TGlobalIndexator<String>) end; begin Index1 := TMyStringIndexator('Key0'); Index2 := TMyStringIndexator('Key1'); end; 


Where more flexibility is required, in addition to the indexed value, you can specify a “client”. Indexing of various clients will be carried out independently. If the client is a static class, then TSomeClass.ClassInfo is specified, if the current descendant is Self.ClassInfo, if the object is, then just Self.



Here, for example, is the implementation of the above mentioned instance counter:



 type TCountable = class private FIndex: Integer; class var FCounts: array of Integer; function GetCount: Integer; inline; public constructor Create; destructor Destroy; override; property Count: Integer read GetCount; end; constructor TCountable.Create; begin FIndex := TGlobalIndexator<Pointer>.GetIndex(TCountable.ClassInfo, ClassInfo); if Length(FCounts) <= FIndex then SetLength(FCounts, FIndex + 1); Inc(FCounts[FIndex]); end; destructor TCountable.Destroy; begin Dec(FCounts[FIndex]); end; function TCountable.GetCount: Integer; begin Result := FCounts[FIndex]; end; 


So each descendant will have its own instance counter, and there is no need for any additional actions.



In general, I hope someone will benefit from this idea.

Source: https://habr.com/ru/post/342868/



All Articles