Včera jsem psal o tom, jak těžké jsou návraty k Delphi, když Vás rozmazlí PHP. Kromě chybějící podpory pro Automatickou Správu Paměti je tu ještě jedna věc, kterou jsem citelně postrádal. A tou je asociativní pole.
Ale i tento problém už je pro mě minulostí 🙂 Může být i pro Vás, stačí stáhnout tento šikovný unit hashes.pas a hned můžete začít asociovat co hrdlo ráčí. Pokud nevíte, co to asociativní pole je, čtěte dál a soubor si určitě stáhněte. Získáte velmi šikovné rozšíření pro Delphi. Rozšíření těžkého kalibru.
Co je asociativní pole
K prvkům obyčejného pole přistupujeme pomocí čísel, tedy indexů.
První prvek pole je například foo[0]
, druhý
foo[1]
, atd. V případě asociativního pole se k prvkům
přistupuje pomocí řetězců (klíčů). To se na první pohled nejeví jako
žádná senzace, ale už na druhý pohled zjistíte, jaké nebývalé možnosti
to skýtá. Podívejte se třeba na příklad:
pole['nazev'] := 'Siemens S65';
pole['barva'] := 'šedá';
pole['kod'] := '22143x';
if (pole['kod'] = '22143x') then ...
Počet prvků v poli se samozřejmě může dynamicky měnit. Rychlý přístup k položkám zajišťuje mechanismus zvaný hashování (proto se unit jmenuje hashes.pas).
Teď už víte, že asociativní pole není pole, které okopávají asociálové, nýbrž skvělý nástroj programátora.
Dokumentace
V knihovně je definovaná základní třída THash a její dva následníci:
- TStringHash – prvky pole jsou řetězce String
- TIntegerHash – prvky pole jsou celá čísla Integer
S popisem třídy THash budeme rychle hotovi: nikdy ji nebudete ve svých aplikacích potřebovat. Slouží jen jako základ pro zmíněné následníky.
A nyní se podíváme na zoubek třídě TStringHash.
Nejprve vytvoříme pole, tedy objekt:
arr := TStringHash.Create();
Asociativní pole rozlišuje u klíčů velká a malá písmenka. Tedy
arr['AHOJ']
a arr['Ahoj']
nejsou tytéž prvky.
Rozlišování je však možné vypnout – nastavením vlastnosti
CaseSensitive na hodnotu false. Tuto vlastnost lze nastavit jen
v případě, že je pole prázdné (nejlépe tedy hned po vytvoření
objektu). Jinak dojde k vyvolání výjimky. (nerozlišování velkých a
malých písmen PHP neumí).
Do asociativního pole můžeme vkládat hodnoty:
arr['key1'] := 'Hello';
arr['key2'] := 'Word';
arr['key2'] := 'World'; // přepíše původní hodnotu
A také z něj číst:
s := arr['key1'];
if (arr['key2'] = 'World') then ...
Abyste měli při práci s polem ještě více pohodlí, doplnil jsem objekt o další vychytávky, které umožní zapisovat a číst i jiné typy, než jen string:
arr.Integers['wow'] := 23;
arr.Floats['yeah'] := 3.14;
arr.Strings['La'] := 'Trine'; // totéž jako arr['La'] := 'Trine'
arr.Booleans['visible'] := true;
arr.Objects['window'] := button;
A obdobně lze hodnoty i číst:
arr['age'] := 26;
born := 2004 - arr.Integers['age']; // 1978
Co je důležité – interně se všechny prvky ukládají jako string. Na ostatní typy se tedy vždy převádějí z řetězce. Možná Vás napadá: co nastane v situaci, kdy typovou konverzi nelze provést:
arr['name'] := 'Homer';
i := arr.Integers['name'];
V takovém případě dojde k vyvolání výjimky. Nicméně i tady
nabízím mechanismus, který Vám život zjednoduší. Třída má vlastnost
UseExceptions pomocí které můžete ovládat chování jednotky
v těchto „krajních“ situacích. Při nastavení
UseExceptions := true
(což je výchozí stav) vyvolá výjimku
každá neproveditelná typová konverze. Naopak při
UseExceptions := false
k vyvolání výjimky nedojde a funkce
vrátí 0, false, prázdný řetězec nebo nil (záleží na typu).
UseExceptions ovlivňuje chování programu i při čtení neexistující položky (jestli má vést k vyvolání výjimky nebo tiše vrátit nulu a při mazání neexistující položky (metoda Delete, popsaná níže).
Hodnotu vlastnosti UseExceptions můžete (na rozdíl od CaseSensitive) měnit kdykoliv.
Poznámka: Zamlčení výjimek a tiché přehlížení „chyb“ může vypadat jako návod k laxnímu programování. O to mi v žádném případě nejde – UseExceptions je jen pomocníkem pro jakousi pre-inicializaci pole.
Další funkce pro práci s asociativním polem:
Test existence klíče:
if (arr.Exists('air')) then Breath();
Smazání klíče:
arr.Delete('WTC');
Přejmenování klíče:
h.Rename('Gary Oldman', 'Paul Newman');
Pokud klíč s novým jménem už existuje, tak je přepsán. Jen připomenu, že metody Delete a Rename vyvolají výjimku při pokusu smazat nebo přejmenovat neexistující klíč. V případě Delete lze toto chování změnit pomoci vlastnosti UseExceptions.
Počet položek v poli:
Count := arr.ItemCount();
Iterace aneb procházení polem.
Znalci PHP jistě vědí, že v tomto případě není možné použít cyklus for, protože asociativní pole není kontinuální. Proto použijeme interní kurzor, se kterým budeme v poli pohybovat.
Nejprve je třeba iteraci zahájit, k tomu slouží metoda Restart. Ta přesune interní ukazatel PØED první položku. A tady PéHáPéčkaři dejte pozor! V PHP k inicializaci kurzoru slouží metoda Reset, která však kurzor přesune NA první položku. To je zásadní rozdíl, proto jsem se rozhodl nechat Delfí metodě trošku jiný název. A v čem je způsob, jakým pracuje Restart, lepší? To je prosté, můžete iterační kód vložit do cyklu while, viz příklad níže.
K pohybu v poli vpřed slouží metoda Next, zpátky Prev. Vrací true, pokud byl posun úspěšný (tj. nejsme na kraji pole), jinak false.
Aktuální klíč vrací metoda Key, hodnotu aktuálního prvku
Current. Chcete-li využít techniky přetypování, použijte volání
např. arr.booleans[arr.Key]
.
A konečně souhrnný příklad na travestii polem nepolem:
arr.Restart;
while (arr.Next) do begin
key := arr.Key;
value := arr.Current;
end;
Poznámka: kdybyste podobný zápis použili v PHP, nezpracuje se první prvek, protože Next v podmínce while ho prostě přeskočí. Proto ta rozdílná funkce příkazů Restart a Reset, kapišto?
Ale pozor! Během procházení polem jej nesmíte změnit (Delete,
Rename, přidávání položek atd.). Jediná přípustná změna je modifikace
hodnoty již existujícího prvku. V ostatních případech je třeba zavolat
Restart a začít s iterací znovu. Pokud se pokusíte pokračovat v iteraci
ve změněném poli, nebo pokud voláte metody Next či Prev bez zahájení
iterace, či snad použijete Key, Current po dokončené iteraci, bude vyvolána
výjimka. Nic na tom nezmění ani UseExceptions := false
. Tohle
jsou totiž indicie, že jste se dopustili hrubé programátorské chyby a kdyby
to šlo, spolu s výjimkou se dostaví i výchovný pohlavek 😉
TIntegerHash
Třída TIntegerHash funguje v postatě stejně, jen s tím rozdílem, že interně ukládá hodnoty jako celá čísla integer. Příklad:
arrI := TIntegerHash.Create();
arrI['age'] := 13;
s := arrI.Strings['age']; // vrací řetězec '13'
Z logických důvodů jsem do TIntegerHash neimplementoval přetypovaní
metody pro reálná čísla (tedy arrI.Floats[]
).
Kdy použít TStringHash a kdy TIntegerHash? Když budete do pole ukládat jen celá čísla nebo objekty (ukazatele na objekty jsou v podstatě také celá čísla), zvolte TIntegerHash, je pro daný úkol optimální. Ve všechny ostatních případech přijde k chuti TStringHash.
Copyright
V jednotka je použita části výborného kódu, jehož autorem je Ciaran McCreesh. Jeho modifikace a užití je v souladu s licencí.
Kód můžete volně používat, pro své potřeby modifikovat, je však třeba vždy uvést původní zdroj a copyright, jak je uveden ve zdrojovém kódu.
p.s. najde se někdo, kdo by přeložil tuto dokumentaci do angličtiny?
Komentáře
Jameson #1
hmmm, hmmm… je sice teprve říjen, ale tohle považuju za článek ROKU !!!
Díky moc!
Joey #2
Používám a našel jsem tam a opravil pár chyb (CaseSensitive u TIntegerHash nepracoval dobře apod.). Ještě to potrápím a pak snad někde zveřejním.
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.