Индексация Глобальная И Не Очень Хорошая

Сразу оговорюсь: статья не имеет никакого отношения к индексации сайтов и т.п.

Мы будем говорить о вещах гораздо более простых, но тем не менее необходимых.

Иногда необходимо индексировать сущности так, чтобы индексы были уникальными внутри программы и компактно упаковывались в интервал [0.N].

Более того, я вообще не хочу создавать для этого отдельные механизмы.

Примером может служить следующая задача: Думаю, все знают, что class var в Delphi — это не что иное, как обычная глобальная переменная, общая для класса и всех его потомков.

А иногда нужно, чтобы дети использовали свои собственные, например, для подсчета экземпляров класса.

я знаю по крайней мере одно решение эта проблема, но это хак.

Кроме того, это требует от пользователя дополнительных действий — выделения памяти в блоке инициализации и по-хорошему освобождения ее при финализации.

Но можно поступить еще проще — создать глобальный (класс 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;

Код по-прежнему не сложен.

Как видите, процедур удаления индекса не существует. Это сделано специально, чтобы избежать многих проблем, связанных с использованием «старого» индекса.

Вы можете использовать этот класс двумя способами: В простых случаях вы можете просто определить нового потомка класса.

Ничего переопределять не нужно, важен только факт создания нового класса.



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

Если требуется большая гибкость, в дополнение к индексированному значению можно указать «клиент».

Индексация разных клиентов будет осуществляться независимо.

Если клиент — статический класс, то указывается TSomeClass.ClassInfo, если текущий потомок — Self.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;

Таким образом, у каждого дочернего элемента будет свой счетчик экземпляров, и никаких дополнительных действий не потребуется.

В общем, надеюсь, эта идея кому-нибудь принесет пользу.

Теги: #delphi #индексация строк #индексация объектов #программирование #delphi

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.