pascalx.lang.table.pas

Переключить прокрутку окна
Загрузить этот исходный код

{
    pascalx.lang.table — таблицы контрольных сумм с ключами типа RefCountInterface,
    AnsiString и UnicodeString.

    Copyright © 2021, 2026 Малик Разработчик

    Это свободная программа: вы можете перераспространять её и/или изменять
    её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она будет полезной,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <https://www.gnu.org/licenses/>.
}

unit pascalx.lang.table;

    {$MODE OBJFPC}

interface

    {%region} uses
        pascalx.lang;
    {%endregion}

    {$TYPEINFO ON}
    {$CALLING REGISTER}

    const UNIT_NAME = 'pascalx.lang.table';

    {%region} type
        generic KeyValueEntry<KeyType: RefCountInterface; ValueType> = class sealed(&Object) { KeyType обязательно должен быть протоколом (interface)! }
        private
            fldIndex: int;
            fldHash: long;
            fldKey: KeyType;
            fldValue: ValueType;
            fldNext: KeyValueEntry;
        public
            constructor create(index: int; hash: long; key: KeyType; const value: ValueType; next: KeyValueEntry);
            property index: int read fldIndex;
            property hash: long read fldHash;
            property key: KeyType read fldKey;
            property value: ValueType read fldValue write fldValue;
            property next: KeyValueEntry read fldNext write fldNext;
        end;

        generic KeyValueTable<KeyType: RefCountInterface; ValueType> = class abstract(&Object) { KeyType обязательно должен быть протоколом (interface)! }
        private type
            TableEntry         = specialize KeyValueEntry<KeyType, ValueType>;
            TableEntry_Array1d = specialize DynamicArray<TableEntry>;
            KeyType_Array1d    = specialize DynamicArray<KeyType>;
        private
            fldLength: int;
            fldTable: TableEntry_Array1d;
            fldKeys: KeyType_Array1d;
            procedure rehash();
            procedure put(key: KeyType; const value: ValueType);
            procedure remove(key: KeyType);
        protected
            procedure setValue(key: KeyType; const value: ValueType);
            function getLength(): int; virtual;
            function contains(key: KeyType): boolean;
            function getValue(key: KeyType): ValueType;
            function getKey(index: int): KeyType;
        public
            constructor create();
            destructor destroy; override;
            procedure clear(); virtual;
            property length: int read getLength;
        end;

        generic Hashtable<KeyType: RefCountInterface; ValueType> = class(specialize KeyValueTable<KeyType, ValueType>) { KeyType обязательно должен быть протоколом (interface)! }
        protected
            procedure setValue(key: KeyType; const value: ValueType); virtual;
            function getValue(key: KeyType): ValueType; virtual;
        public
            function contains(key: KeyType): boolean; virtual;
            function keyAt(index: int): KeyType; virtual;
            property value[key: KeyType]: ValueType read getValue write setValue; default;
        end;

        generic HashtableOfAnsiString<ValueType> = class(specialize KeyValueTable<Value, ValueType>)
        protected
            procedure setValue(const key: AnsiString; const value: ValueType); virtual;
            function getValue(const key: AnsiString): ValueType; virtual;
        public
            function contains(const key: AnsiString): boolean; virtual;
            function keyAt(index: int): AnsiString; virtual;
            property value[const key: AnsiString]: ValueType read getValue write setValue; default;
        end;

        generic HashtableOfUnicodeString<ValueType> = class(specialize KeyValueTable<Value, ValueType>)
        protected
            procedure setValue(const key: UnicodeString; const value: ValueType); virtual;
            function getValue(const key: UnicodeString): ValueType; virtual;
        public
            function contains(const key: UnicodeString): boolean; virtual;
            function keyAt(index: int): UnicodeString; virtual;
            property value[const key: UnicodeString]: ValueType read getValue write setValue; default;
        end;
    {%endregion}

implementation

    {$CALLING REGISTER}

    {%region  KeyValueEntry }
        constructor KeyValueEntry.create(index: int; hash: long; key: KeyType; const value: ValueType; next: KeyValueEntry);
        begin
            inherited create();
            fldIndex := index;
            fldHash := hash;
            fldKey := key;
            fldValue := value;
            fldNext := next;
        end;
    {%endregion}

    {%region  KeyValueTable }
        procedure KeyValueTable.rehash();
        var
            oldCap: int;
            oldIdx: int;
            newCap: int;
            newIdx: int;
            oldEntry: TableEntry;
            newEntry: TableEntry;
            oldTable: TableEntry_Array1d;
            newTable: TableEntry_Array1d;
            newArray: KeyType_Array1d;
        begin
            oldTable := fldTable;
            oldCap := system.length(oldTable);
            newCap := (oldCap shl 1) or 1;
            if newCap < 0 then begin
                raise BufferTooLargeError.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, '!error.buffer-too-large'));
            end;
            newTable := TableEntry_Array1d(&Array.newTObject1d(newCap));
            for oldIdx := oldCap - 1 downto 0 do begin
                oldEntry := oldTable[oldIdx];
                while oldEntry <> nil do begin
                    newIdx := int((oldEntry.hash and CoLong.MAX_VALUE) mod newCap);
                    newEntry := oldEntry;
                    oldEntry := oldEntry.next;
                    newEntry.next := newTable[newIdx];
                    newTable[newIdx] := newEntry;
                end;
            end;
            newArray := KeyType_Array1d(&Array.newIUnknown1d(newCap));
            &Array.copyUnknowns(fldKeys, 0, newArray, 0, oldCap);
            fldTable := newTable;
            fldKeys := newArray;
        end;

        procedure KeyValueTable.put(key: KeyType; const value: ValueType);
        var
            idx: int;
            len: int;
            cap: int;
            hash: long;
            kobj: TObject;
            entry: TableEntry;
            table: TableEntry_Array1d;
        begin
            table := fldTable;
            kobj := key as TObject;
            hash := key.getHashCode();
            cap := system.length(table);
            idx := int((hash and CoLong.MAX_VALUE) mod cap);
            entry := table[idx];
            while entry <> nil do begin
                if (entry.hash = hash) and entry.key.equals(kobj) then begin
                    entry.value := value;
                    exit;
                end;
                entry := entry.next;
            end;
            len := fldLength;
            if len >= cap then begin
                rehash();
                table := fldTable;
                idx := int((hash and CoLong.MAX_VALUE) mod system.length(table));
            end;
            fldKeys[len] := key;
            table[idx] := TableEntry.create(len, hash, key, value, table[idx]);
            fldLength := len + 1;
        end;

        procedure KeyValueTable.remove(key: KeyType);
        var
            idx: int;
            len: int;
            hash: long;
            kobj: TObject;
            prev: TableEntry;
            entry: TableEntry;
            table: TableEntry_Array1d;
            keys: KeyType_Array1d;
        begin
            table := fldTable;
            kobj := key as TObject;
            hash := key.getHashCode();
            idx := int((hash and CoLong.MAX_VALUE) mod system.length(table));
            prev := nil;
            entry := table[idx];
            while entry <> nil do begin
                if (entry.hash = hash) and entry.key.equals(kobj) then begin
                    if prev <> nil then begin
                        prev.next := entry.next;
                    end else begin
                        table[idx] := entry.next;
                    end;
                    idx := entry.index;
                    keys := fldKeys;
                    len := fldLength - 1;
                    &Array.copyUnknowns(keys, idx + 1, keys, idx, len - idx);
                    keys[len] := nil;
                    fldLength := len;
                    entry.free();
                    exit;
                end;
                prev := entry;
                entry := entry.next;
            end;
        end;

        procedure KeyValueTable.setValue(key: KeyType; const value: ValueType);
        begin
            if key = nil then begin
                raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('key') ]));
            end;
            if value = default(ValueType) then begin
                remove(key);
                exit;
            end;
            put(key, value);
        end;

        function KeyValueTable.getLength(): int;
        begin
            result := fldLength;
        end;

        function KeyValueTable.contains(key: KeyType): boolean;
        var
            hash: long;
            kobj: TObject;
            entry: TableEntry;
            table: TableEntry_Array1d;
        begin
            if key = nil then begin
                result := false;
                exit;
            end;
            table := fldTable;
            kobj := key as TObject;
            hash := key.getHashCode();
            entry := table[int((hash and CoLong.MAX_VALUE) mod system.length(table))];
            while entry <> nil do begin
                if (entry.hash = hash) and entry.key.equals(kobj) then begin
                    result := true;
                    exit;
                end;
                entry := entry.next;
            end;
            result := false;
        end;

        function KeyValueTable.getValue(key: KeyType): ValueType;
        var
            hash: long;
            kobj: TObject;
            entry: TableEntry;
            table: TableEntry_Array1d;
        begin
            if key = nil then begin
                raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('key') ]));
            end;
            table := fldTable;
            kobj := key as TObject;
            hash := key.getHashCode();
            entry := table[int((hash and CoLong.MAX_VALUE) mod system.length(table))];
            while entry <> nil do begin
                if (entry.hash = hash) and entry.key.equals(kobj) then begin
                    result := entry.value;
                    exit;
                end;
                entry := entry.next;
            end;
            result := default(ValueType);
        end;

        function KeyValueTable.getKey(index: int): KeyType;
        begin
            if (index < 0) or (index >= fldLength) then begin
                raise IndexOutOfBoundsException.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'out-of-bounds.index'));
            end;
            result := fldKeys[index];
        end;

        constructor KeyValueTable.create();
        begin
            inherited create();
            fldTable := TableEntry_Array1d(&Array.newTObject1d($0f));
            fldKeys := KeyType_Array1d(&Array.newIUnknown1d($0f));
        end;

        destructor KeyValueTable.destroy;
        var
            idx: int;
            next:  TableEntry;
            entry: TableEntry;
            table: TableEntry_Array1d;
        begin
            table := fldTable;
            for idx := system.length(table) - 1 downto 0 do begin
                entry := table[idx];
                while entry <> nil do begin
                    next := entry.next;
                    entry.free();
                    entry := next;
                end;
            end;
            inherited destroy;
        end;

        procedure KeyValueTable.clear();
        var
            idx: int;
            next:  TableEntry;
            entry: TableEntry;
            table: TableEntry_Array1d;
        begin
            table := fldTable;
            for idx := system.length(table) - 1 downto 0 do begin
                entry := table[idx];
                while entry <> nil do begin
                    next := entry.next;
                    entry.free();
                    entry := next;
                end;
                table[idx] := nil;
            end;
            &Array.fillUnknowns(fldKeys, 0, fldLength, nil);
            fldLength := 0;
        end;
    {%endregion}

    {%region  Hashtable }
        procedure Hashtable.setValue(key: KeyType; const value: ValueType);
        begin
            inherited setValue(key, value);
        end;

        function Hashtable.getValue(key: KeyType): ValueType;
        begin
            result := inherited getValue(key);
        end;

        function Hashtable.contains(key: KeyType): boolean;
        begin
            result := inherited contains(key);
        end;

        function Hashtable.keyAt(index: int): KeyType;
        begin
            result := inherited getKey(index);
        end;
    {%endregion}

    {%region  HashtableOfAnsiString }
        procedure HashtableOfAnsiString.setValue(const key: AnsiString; const value: ValueType);
        begin
            inherited setValue(CoAnsiString.create(key), value);
        end;

        function HashtableOfAnsiString.getValue(const key: AnsiString): ValueType;
        begin
            result := inherited getValue(CoAnsiString.create(key));
        end;

        function HashtableOfAnsiString.contains(const key: AnsiString): boolean;
        begin
            result := inherited contains(CoAnsiString.create(key));
        end;

        function HashtableOfAnsiString.keyAt(index: int): AnsiString;
        begin
            result := inherited getKey(index).asAnsiString();
        end;
    {%endregion}

    {%region  HashtableOfUnicodeString }
        procedure HashtableOfUnicodeString.setValue(const key: UnicodeString; const value: ValueType);
        begin
            inherited setValue(CoUnicodeString.create(key), value);
        end;

        function HashtableOfUnicodeString.getValue(const key: UnicodeString): ValueType;
        begin
            result := inherited getValue(CoUnicodeString.create(key));
        end;

        function HashtableOfUnicodeString.contains(const key: UnicodeString): boolean;
        begin
            result := inherited contains(CoUnicodeString.create(key));
        end;

        function HashtableOfUnicodeString.keyAt(index: int): UnicodeString;
        begin
            result := inherited getKey(index).asUnicodeString();
        end;
    {%endregion}

end.