Сразу оговорюсь: статья не имеет никакого отношения к индексации сайтов и т.п.
Мы будем говорить о вещах гораздо более простых, но тем не менее необходимых.
Иногда необходимо индексировать сущности так, чтобы индексы были уникальными внутри программы и компактно упаковывались в интервал [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
-
Выбор Лучшего Гаджета В Подарок
19 Oct, 24 -
Словообразование
19 Oct, 24 -
Облигация С Нулевым Купоном
19 Oct, 24 -
Драйвер Sql Server 2005 Для Php
19 Oct, 24