インデックス作成はグローバルであり、それほどではありません

すぐに予約すると、記事はサイトのインデックス作成などとは関係ありません。 物事についてもっと簡単に話しますが、それでも必要です。 インデックスをプログラム内で一意にし、間隔[0..N]でコンパクトにパックされるように、エンティティにインデックスを付ける必要がある場合があります。 そして、このために別のメカニズムを開始したくありません。



例は次のタスクです。



Delphiのクラス変数は、クラスとそのすべての子孫に共通する通常のグローバル変数にすぎないことを知っていると思います。 また、場合によっては、子孫はクラスのインスタンスをカウントするなどのために独自のものを使用する必要があります。 この問題の解決策を少なくとも1つ知っていますが、これはハックです。 さらに、ユーザーは追加のアクションを実行する必要があります-初期化ブロックでメモリを割り当て、良い方法で、ファイナライズで解放します。



しかし、さらに簡単にすることができます-グローバル(クラスvar)配列を作成し、すべての子が独自のセルを参照するようにします。 唯一の問題は、子孫のインデックス付けが必要であり、これを自動的に行うことです。



私の場合、タスクはわずかに異なっていました。 セットに自分自身を含めたり除外したり、O(1)に属しているかどうかをチェックする普遍的な機能をクラスに追加したかった つまり、提案されたセットの数に関係なく、そのような機会を与える「署名」フィールドを追加し、クラスを共通の祖先に関連付けません。 いずれにせよ、特定の段階で、これらのセットをインデックス化するという問題に遭遇しました。



インターネットをさまよった後、既成のソリューションは見つかりませんでした。 実際、そのようなタスクがまったく提起されたとは思いませんでした。 一方、これは多くのアプリケーションを見つけたと思います。



一般的に、タスクは難しくありません。 おそらく、これがこのトピックに関する話がなかった理由でした。 最も原始的な形式では、コードは数行しかかかりません。



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;
      
      





しかし、私には十分ではないようでした。 このような実装では、追加の変数を原則としてグローバルに作成し、それらの初期化を監視する必要があります。 さらに、柔軟性が不足しています。 一般に、私はアプローチを少し改善することにしました。結果は次のとおりです。



 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;
      
      





コードはまだ複雑ではありません。 ご覧のとおり、インデックスの削除手順はありません。 これは、特に「古い」インデックスの使用に関連する多くの問題を回避するために行われます。



このクラスを適用するには、2つの方法があります。単純な場合、新しいクラスの子孫を単純に指定できます。 何も再定義する必要はありません。新しいクラスを作成するという事実だけが重要です。



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





より高い柔軟性が必要な場合は、インデックス値に加えて「顧客」を指定できます。 さまざまな顧客のインデックス作成は独立して実行されます。 クライアントが静的クラスの場合、現在の子がSelf.ClassInfoの場合はTSomeClass.ClassInfoが示され、オブジェクトの場合はSelfのみが示されます。



たとえば、上記のインスタンスカウンターの実装は次のとおりです。



 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;
      
      





したがって、各子孫には独自のインスタンスカウンターがあり、追加のアクションは必要ありません。



一般的に、誰かがこのアイデアから利益を得ることを願っています。



All Articles