Tablice asocjacyjne w Delphi
bidok
Wstęp
"Tablica asocjacyjna (mapa, słownik) (ang. associative array, map, dictionary) to pojemnik zawierający wartości indeksowane przez klucze. [...] Wiele złożonych danych jest naturalnie reprezentowanych przez tego typu tablice - np. drzewa plików, nagłówki poczty, a nawet wszystkie atrybuty obiektu czy przestrzeń nazw zmiennych."[#]_
Jak wszyscy wiemy Delphi nie oferuje w standardzie takiego typu tablicy, który co niektórym jest dobrze znany na przykład z php. Zastosowanie tablicy asocjacyjnej w bardzo wielu sytuacjach ułatwia prace programiście i właśnie dlatego napisałem odpowienią klasę którą chciałbym się podzielić.
Zastosowanie
<font color="red">Nowe!</span> Ustawienia:
Od wersji 1.1 jest zaimplementowany przełącznik łatwo pozwalający zmienić niektóre zasady działania klasy. Teraz zamiast wskaźników można stosować wygodniejsze varianty. To w jaki sposób chce się korzystać zależy tylko od własnych upodobań. Aby "Przełączyć" wystarczy odkomentować jedna linie w AssocArray.pas tak jak niżej:
{ varianty wyłączone, pointery włączone }
// {$DEFINE USE_VARIANTS}
{ varianty włączone, pointery wyłączone }
{$DEFINE USE_VARIANTS}
Tworzenie i usuwanie:
Listę tworzy się jak każdą inna klasę i tak samo się usuwa (należy jednak pamiętać by nie zostawiać po sobie w pamięci tego co utworzliśmy za pomocą New lub GetMem).
var
Lista: TAssocArray;
begin
Lista := TAssocArray.Create;
// [...]
Lista.Destroy;
end;
Zapis danych:
Zapis (lub modyfikowanie) listy odbywa się w taki sposób:
Lista[Klucz] := Wskaznik;
// lub (w zależności czy mamy ustawione USE_VARIANTS )
Lista[Klucz] := Zmienna;
Nie musimy się martwić czy dany klucz istnieje czy też nie, tak samo nie musimy martwić się czy nasza lista będzie miałą wystarczającą wielkość. W razie potrzeby lista sama zarezerwuje odpowiednią ilość pamięci.
Odczyt danych:
Analogicznie do zapisu
Wskaznik := Lista[Klucz];
// lub (w zależności czy mamy ustawione USE_VARIANTS )
Zmienna := Lista[Klucz];
W tym miejscu należy jednak pamiętać, że Lista[Klucz] może zwrócić wartość Nil gdy nie istnieje żadna wartość pod danym kluczem.
Usuwanie elementu listy:
Aby usunąć element listy gdy nie jest nam już potrzebny wykonujemy prostą operację przypisania wartości Nil lub Null dla danego klucza:
Lista[Klucz] := nil;
// lub (w zależności czy mamy ustawione USE_VARIANTS )
Lista[Klucz] := Null;
// Null można stosować również gdy use_variants jest wyłączone (const null = nil;) jednak gdy jest włączone null jest funkcją która zwraca Variant będący nil.. pokręcone ale musi tak być
Spowoduje to usunięcie i zwolnienie pamięci zajmowanej przez element. Lista będzie mniejsza, bardziej czysta i szybsza.
Gdy chcemy usunąć wszystkie elementy wystarczy użyć procedury
Lista.Clear;
Pętla
By odczytać wszystkie wartości listy najwygodniej użyć pętli While i funkcji Lista.GetNextItem, tak jak w kodzie niżej:
var
Temp: PAssocItem;
begin
Temp := nil;
while Lista.GetNextItem(Temp) do
begin
// dane które przypisaliśmy do Lista[Klucz] znajdują się w Temp^.Value
// w Temp^.Name jest Klucz pod którym dane występują na liście
// Temp^.Next jest wskaźnikiem do następnego elementu listy
// dla zapewnienia stabilności lepiej tego ostatniego nie modyfikowac ^_^
end;
end;
Możliwe klucze:
Kluczem dla listy mogą być liczby całkowite, liczby rzeczywiste oraz tekst na przykład kod:
// Gdy USE_VARIANTS jest wyłączony
const
s1 = 'string numer 1';
s2 = 'string numer 2';
s3 = 123456789;
begin
Lista['Kowalski'] := @s1;
Lista[123456] := @s2;
Lista[3.14] := @s3;
end;
// Gdy USE_VARIANTS jest włączony
begin
Lista['Nazwisko'] := 'Jan Marian';
Lista[123] := 'Przykładowy tekst';
Lista[3.14] := Integer(@s2);
end;
jest jak najbardziej poprawny.
Przykładowy kod
Zdaję sobie sprawę, że czasami o wiele lepiej człowiek uczy się z przykładu, więc takowy zamieszczam.
type
PDane = ^TDane;
TDane = record
Imie: string;
Nazwisko: string;
NrButa: integer;
end;
var
Lista: TAssocArray; // nie zapomnieć utworzyć!
// zwróci false jesli user juz istnieje
function AddUser(UID, Imie, Nazwisko: string; NrButa: Integer): Boolean;
var
Nowy: PDane;
begin
Result := False;
if Lista[UID] <> nil then Exit;
New(Nowy);
Nowy^.Imie := Imie;
Nowy^.Nazwisko := Nazwisko;
Nowy^.NrButa := NrButa;
Lista[UID] := Nowy;
Result := True;
end;
// zmienia dane usera.. jesli nie istnieje to go dodaje i zwraca false
function ModifyUser(UID, Imie, Nazwisko: string; NrButa: Integer): Boolean;
var
Temp: PDane;
begin
Temp := PDane(Lista[UID]);
if Temp = nil then
begin
AddUser(UID, Imie, Nazwisko, NrButa);
Result := False;
end;
Temp^.Imie := Imie;
Temp^.Nazwisko := Nazwisko;
Temp^.NrButa := NrButa;
Result := True;
end;
// zmienia tylko nr buta, pod warunkiem ze user istnieje
procedure ZmienNrButa(UID: string; NrButa: Integer);
begin
if Lista[UID] <> nil then
PDane(Lista[UID])^.NrButa := NrButa;
end;
// usuwa usera
function DeleteUser(UID: string): Boolean;
var
Temp: PDane;
begin
Temp := Lista[UID];
if Temp <> nil then
begin
Dispose(Temp);
Lista[UID] := nil;
Result := True;
end else
Result := False;
end;
procedure ListUsers(var L: TStrings);
var
Temp: PAssocItem;
Dane: PDane;
begin
L.Clear;
Temp := nil;
while Lista.GetNextItem(Temp) do
begin
Dane := PDane(Temp^.Value);
L.Add(Format('[%s] Imie: %s, Nazwisko: %s, Numer buta: %d', [Temp^.Name, Dane^.Imie, Dane^.Nazwisko, Dane^.NrButa]));
end;
end;
Załącznik
AssocArray.zip
AssocArray1.1.zip
.. [#] Źródło: <wiki href="Tablica_asocjacyjna">Wikipedia</wiki>
Dzięki wielkie, naprawdę mi się przydało :)
Jak tylko dojde do tego jak podmienic załącznik to umieszcze nowsza wersje..
edit: zrobione
nie zawsze jest to wygodne w używaniu - te pointery, no ale ogólnie to mechanizm chyba nawet sprawny (GetItem itp)
Mim zdaniem to bardziej art niż gotowiec :/ Ale nie będę przenosił, bo jeszcze jakiś mod mnie okrzyczy albo coś :) :)