Hlavní navigace

Operátory a asociativní pole v jazyku Lua

24. 3. 2009
Doba čtení: 13 minut

Sdílet

Ve třetí části seriálu o programovacím jazyku Lua si popíšeme všechny operátory nabízené tímto jazykem a taktéž se zmíníme o práci s asociativními poli, protože tento datový typ představuje základ jak pro tvorbu "klasických" indexovaných polí, tak i seznamů, zásobníků, front, stromů, struktur typu záznam a dokonce i objektů.

Obsah

1. Aritmetické operátory
2. Logické operátory
3. Relační operátory
4. Spojení řetězců a operátor vracející délku struktury
5. Priority operátorů
6. Asociativní pole
7. Vytvoření asociativního pole
8. Přístup k prvkům asociativního pole

1. Aritmetické operátory

Programovací jazyk Lua obsahuje celkem sedm základních aritmetických operátorů aplikovatelných na číselné hodnoty či proměnné (viz předchozí část tohoto seriálu), popř. na řetězce převedené automaticky na čísla. Jedná se o součet, rozdíl, součin, podíl (neceločíselný), dělení modulo (zbytek po dělení), umocnění a v neposlední řadě též unární minus (změna znaménka); všechny zmíněné operátory jsou vypsány v tabulce pod odstavcem. Při práci s číselnými hodnotami si musíme dát pozor na některé problémy, které mohou vznikat na základě zvolené reprezentace čísel. Pokud používáme standardně přeložený interpretr jazyka Lua, jsme v přesnosti i rozsahu využitelných (reprezentova­telných) hodnot omezeni 64bitovým datovým typem double definovaným v normě IEEE 754. Nejvyšší reprezentovatelná hodnota dosahuje 1,8×10308, nejmenší možná nenulová kladná hodnota je rovna 2,2×10-308. Pro exponent je vyhrazeno 11 bitů a pro mantisu zbývajících 53 bitů (přesněji 52 bitů plus bit znaménkový), což pro nás jako programátory znamená, že pouze 53bitová celá čísla lze v tomto formátu reprezentovat zcela přesně, po překročení tohoto rozsahu již dochází k zaokrouhlení či ořezání nejnižších bitů zpracovávané hodnoty (jinými slovy – typ long se obecně na typ double převede s chybou, přijdeme totiž o informace uložené v posledních 11 bitech s nejnižší váhou).

Symbol operátoru Arita Popis
+ binární součet
- binární rozdíl
* binární součin
/ binární podíl (reálná čísla)
% binární dělení modulo
^ binární umocňování
- unární unární minus

Následuje ukázka krátkého demonstračního programu (skriptu) napsaného v jazyce Lua, na kterém je vysvětlen způsob použití všech výše popsaných aritmetických operátorů při výpočtu součtu, rozdílu, součinu, podílu i zbytku po dělení dvou konstant, stejně jako umocnění první konstanty konstantou druhou (xy). Pomocí operátoru umocňování lze vypočítat i druhou odmocninu jakékoli nezáporné hodnoty bez nutnosti použití knihovní funkce math.sqrt() (základní knihovní funkce si popíšeme v některé z dalších částí tohoto seriálu). V demonstračním příkladu je vypočtena druhá odmocnina hodnoty 2. Program ve své závěrečné části vypíše dvě tabulky – první tabulka obsahuje celé kladné mocniny dvojky (tyto hodnoty programátoři většinou znají nazpaměť :-) a následně i záporné mocniny dvojky (tj. vlastně hodnoty 1/(2n), protože záporný exponent odpovídá výpočtu převrácené hodnoty hodnoty s kladným exponentem). Zdrojový kód demonstračního příkladu je velmi jednoduchý:

-- priklad na pouziti aritmetickych operatoru

-- promenne, se kterymi se budou vypocty provadet
a=10
b=20

-- vypiseme hodnoty promennych
print("a=", a)
print("b=", b)

-- vysledky aritmetickych vyrazu
print("a+b=", a+b)
print("a-b=", a-b)
print("a*b=", a*b)
print("a/b=", a/b) -- vysledkem teto operace je realne cislo
print("a%b=", a%b)
print("a^b=", a^b)

-- pomoci operatoru umocnovani lze vypocitat i odmocninu
print("sqrt(2)=", 2^(0.5))

-- vypocet celych kladnych mocnin zakladu 2
print()
print("mocniny cisla 2")

print("x", "2^x")
for x=0,16 do
    print(x, 2^x)
end

-- vypocet celych zapornych mocnin zakladu 2
print()
print("x", "2^(-x)")
for x=0,16 do
    print(x, 2^(-x))
end

-- finito 

Výše uvedený program po svém spuštění vygeneruje následující výstup:

a=        10
b=        20
a+b=      30
a-b=      -10
a*b=      200
a/b=      0.5
a%b=      10
a^b=      1e+020
sqrt(2)=  1.4142135623731

mocniny cisla 2
x         2^x
0         1
1         2
2         4
3         8
4         16
5         32
6         64
7         128
8         256
9         512
10        1024
11        2048
12        4096
13        8192
14        16384
15        32768
16        65536

x         2^(-x)
0         1
1         0.5
2         0.25
3         0.125
4         0.0625
5         0.03125
6         0.015625
7         0.0078125
8         0.00390625
9         0.001953125
10        0.0009765625
11        0.00048828125
12        0.000244140625
13        0.0001220703125
14        6.103515625e-005
15        3.0517578125e-005
16        1.52587890625e-005 

Při použití operátoru umocnění si musíme dát pozor na jeho asociativitu, která je opačná, než u ostatních operátorů (umocnění je automaticky „uzávorkováno“ zprava). V následujícím demonstračním příkladu je ukázáno, jakým způsobem je vypočten výraz a^b^c – výsledek je shodný, jako při použití výrazu a^(b^c), tj. implicitní závorky jsou vloženy zprava a nikoli zleva, tak, jako tomu je u většiny ostatních binárních operátorů:

-- test asociativity operatoru umocnovani

-- promenne, ktere v programu pouzijeme
a=2
b=3
c=4

-- vypis hodnot promennych
print("a=", a)
print("b=", b)
print("c=", c)

-- vypis vysledku vyrazu s operatorem umocnovani
print("a^b^c=", a^b^c)
print("(a^b)^c=", (a^b)^c)
print("a^(b^c)=", a^(b^c))

-- finito 

Výstup z výše uvedeného programu po jeho spuštění:

a=        2
b=        3
c=        4
a^b^c=    2.4178516392293e+024
(a^b)^c=  4096
a^(b^c)=  2.4178516392293e+024 

Následuje nepatrně složitější příklad, pomocí něhož lze vypočítat několik prvních číslic hodnoty π pomocí opakovaného výpočtu aritmetického a geometrického průměru dvou proměnných, jejichž počáteční hodnota je nastavena na 0, resp. 1/2. Převrácená hodnota obou proměnných se postupně přibližuje k hodnotě čísla π (ve skutečnosti se nejedná o nijak rychlý ani efektivní algoritmus, je však velmi jednoduchý):

-- prakticke pouziti aritmetickych operatoru
-- pri iterativnim vypoctu cisla Pi

-- hodnoty vstupujici do vypoctu
a=0
b=1/2
for x=1, 30 do
    -- aritmeticky prumer
    a=(a+b)/2
    -- geometricky prumer
    b=(a*b)^(1/2)
    -- k cislu Pi se priblizujeme zespodu od
    -- dvojky (1/b) a zvrchu od ctverky (1/a)
    print(x, 1/a, 1/b)
end

-- finito 

Výstup programu:

1       4               2.8284271247462
2       3.3137084989848 3.0614674589207
3       3.1825978780745 3.1214451522581
4       3.1517249074293 3.1365484905459
5       3.1441183852459 3.1403311569548
6       3.1422236299425 3.1412772509328
7       3.141750369169  3.1415138011443
8       3.1416320807032 3.1415729403671
9       3.1416025102568 3.1415877252772
10      3.1415951177496 3.1415914215112
11      3.1415932696293 3.1415923455701
12      3.1415928075996 3.1415925765849
13      3.1415926920923 3.1415926343386
14      3.1415926632154 3.141592648777
15      3.1415926559962 3.1415926523866
16      3.1415926541914 3.141592653289
17      3.1415926537402 3.1415926535146
18      3.1415926536274 3.141592653571
19      3.1415926535992 3.1415926535851
20      3.1415926535921 3.1415926535886
21      3.1415926535904 3.1415926535895
22      3.1415926535899 3.1415926535897
23      3.1415926535898 3.1415926535898
24      3.1415926535898 3.1415926535898
25      3.1415926535898 3.1415926535898
26      3.1415926535898 3.1415926535898
27      3.1415926535898 3.1415926535898
28      3.1415926535898 3.1415926535898
29      3.1415926535898 3.1415926535898
30      3.1415926535898 3.1415926535898 

2. Logické operátory

Při zápisu logických výrazů v podmínkách a programových smyčkách se často využívají logické operátory. V programovacím jazyku Lua nejsou tyto operátory reprezentovány speciálními symboly (tak jako u jazyků odvozených od Céčka), ale klíčovými slovy and (logický součin), or (logický součet) a not (logická negace) – viz tabulku vypsanou pod odstavcem. Jako operandy logických operátorů mohou vystupovat jak logické proměnné a konstanty (true, false), tak i hodnoty dalších datových typů, přičemž pouze hodnoty false a nil jsou považovány za „nepravdu“, všechny ostatní hodnoty jsou definitoricky pravdivé (pozor – některé jiné jazyky považují za hodnotu „nepravda“ i prázdné řetězce, v Lua to však neplatí, jak si ostatně ukážeme na demonstračním příkladu). Následuje tabulka s trojicí podporovaných logických operátorů:

Symbol operátoru Arita Popis
and binární logický součin
or binární logický součet
not unární logická negace

Od mnoha dalších programovacích jazyků se Lua odlišuje v tom, že výsledkem vyhodnocení logických operátorů nemusí být nutně jedna z hodnot true či false, ale přímo jeden z operandů – pokud je hodnota prvního operandu u operátoru and rovna false či nil, je tato hodnota přímo vrácena bez její další konverze (pokud se jedná o výraz, je samozřejmě následně vyčíslen). V opačném případě je vrácena hodnota druhého operandu bez jeho převodu na logickou hodnotu. Logický operátor or pracuje podobně – pokud je jeho první operand rozdílný od false či nil, vrátí přímo jeho hodnotu, v opačném případě vrátí druhý operand (mnoho podmínek lze díky tomuto chování nahradit pouze logickým výrazem). Oba zmíněné logické operátory vyhodnocují druhý operand pouze v případě nutnosti, tj. pokud výsledek není známý už z vyhodnocení operandu prvního (jedná se o takzvané zkrácené vyhodnocování neboli short cut evaluation). Tato vlastnost vede k častému používání idiomu typu if t~=nil and t.currentline>=0 then, který by nemohl být použit u jazyků, které zkrácené vyhodnocování logických výrazů neprovádějí.

Následuje příklad, který některé vlastnosti logických operátorů přiblíží:

-- logicke operatory
print()
print("logicky operator and")
print("false and false  =", false and false)
print("true  and false  =", true  and false)
print("false and true   =", false and true )
print("true  and true   =", true  and true )
print("true  and 'ahoj' =", true  and 'ahoj')
print("true  and nil    =", true  and nil)
print("false and 'ahoj' =", false and 'ahoj')
print("nil   and 'ahoj' =", nil   and 'ahoj')
print("'ahoj' and nil   =", 'ahoj'and nil)

print()
print("logicky operator or")
print("false or false  =", false or false)
print("true  or false  =", true  or false)
print("false or true   =", false or true )
print("true  or true   =", true  or true )
print("true  or 'ahoj' =", true  or 'ahoj')
print("true  or nil    =", true  or nil)
print("false or 'ahoj' =", false or 'ahoj')
print("nil   or 'ahoj' =", nil   or 'ahoj')
print("'ahoj' or nil   =", 'ahoj'or nil)

print()
print("logicky operator not")
print("not false=", not false)
print("not true =", not true)
print("not nil  =", not nil)
print("not ''   =", not '') -- prazdny retezec je "pravdivy"
print("not not ''=", not not '')

-- finito 

Výstup příkladu po jeho spuštění:

logicky operator and
false and false  =      false
true  and false  =      false
false and true   =      false
true  and true   =      true
true  and 'ahoj' =      ahoj
true  and nil    =      nil
false and 'ahoj' =      false
nil   and 'ahoj' =      nil
'ahoj' and nil   =      nil

logicky operator or
false or false  =       false
true  or false  =       true
false or true   =       true
true  or true   =       true
true  or 'ahoj' =       true
true  or nil    =       true
false or 'ahoj' =       ahoj
nil   or 'ahoj' =       ahoj
'ahoj' or nil   =       ahoj

logicky operator not
not false=      true
not true =      false
not nil  =      true
not ''   =      false
not not ''=     true 

3. Relační operátory

Programovací jazyk Lua obsahuje obvyklou sadu šesti relačních operátorů vracejících pravdivostní hodnotu true či false, které jsou vypsány v tabulce umístěné pod tímto odstavcem. Tyto operátory mohou sloužit jak pro vzájemné porovnání číselných hodnot, tak i řetězců (lexikografické porovnání) a v některých případech i dalších datových typů (například u tabulek jsou porovnávány reference, nikoli jejich obsah). Důležitý je především operátor rovnosti == a jeho negace ~=. Tento operátor nejprve porovnává typ obou operandů a pokud se typy liší, vrací se automaticky hodnota false (nedojde tedy k automatické konverzi!). Teprve v případě, že jsou typy obou operandů shodné, dojde k jejich skutečnému porovnání. V praxi to například znamená, že výsledek porovnání „10“==10 bude vždy false, i když v jiných případech (aritmetické operátory atd.) by Lua v případě potřeby provedla konverzi řetězce na číselnou hodnotu či naopak konverzi číselné hodnoty na řetězec.

Symbol operátoru Arita Popis
== binární rovnost
~= binární nerovnost
< binární menší než
> binární větší než
<= binární menší nebo rovno
>= binární větší nebo rovno

Následuje demonstrační příklad, ve kterém jsou relační operátory použity pro porovnání řetězců i číselných hodnot:

-- lexikograficke porovnani retezcu
print("'a'<'b': ", "a" < "b")
print("'a'<='b': ", "a" <= "b")
print("'aa' < 'ab': ", "aa" < "ab")
print("'aaaaaa' ~= 'aaaaab'", "aaaaaa" ~= "aaaaab")

-- pozor, operator == nejprve porovnava
-- typy operandu a az po jejich shode
-- vlastni hodnoty
print('"a"=="a":',   "a"=="a")
print('10==10:',     10==10)
print('"10"=="10":', "10"=="10")
print('"10"==10:',   "10"==10) -- !!!!

-- finito 

Výstup programu:

'a'<'b':          true
'a'<='b':         true
'aa' < 'ab':      true
'aaaaaa' ~= 'aaaaab' true
"a"=="a":            true
10==10:              true
"10"=="10":          true
"10"==10:            false 

4. Spojení řetězců a operátor vracející délku struktury

Dva poslední operátory, se kterými se dnes seznámíme, slouží ke spojení dvou řetězců a zjištění délky řetězce či jiné datové struktury (typicky asociativního pole). Řetězce je možné spojit pomocí operátoru zapisovaného dvojicí teček ... Tento (na první pohled možná poněkud nadbytečný) operátor byl do jazyka přidán zejména kvůli automatickým typovým konverzím, protože výraz typu „10“+„20“ je vyhodnocen – nezávisle na tom, že oba operandy jsou řetězce – na hodnotu 30 v důsledku výše zmíněné automatické typové konverze. Unární operátor # vrací délku řetězce nebo jiné datové struktury (pole). Například příkaz print(#„Hello world!“) vypíše na standardní výstup číslo 12. Vypočtená délka se liší podle toho, na jaký typ dat je operátor # aplikován. U řetězců se vrátí počet bajtů nutných pro jejich uložení do operační paměti (ve skutečnosti je nutné kromě vlastního řetězce do paměti uložit i jeho délku, ta se však do výsledku nezapočítá) u asociativního pole s celočíselnými (po sobě jdoucími) indexy pak počet jeho prvků – viz další kapitoly.

Symbol operátoru Arita Popis
.. binární spojení dvou řetězců nebo číslic převedených na řetězec
# unární délka řetězce nebo velikost asociativního pole

5. Priority operátorů

Jednotlivé operátory popsané v předchozích kapitolách je možné rozdělit podle jejich priority do osmi skupin. Priorita, pomocí níž se určuje, které části výrazu se vyhodnotí nejdříve, se určuje již při překladu programu. Operátorem s nejvyšší prioritou je umocňování ^, naopak nejnižší prioritu má logický operátor or. Při pohledu na níže uvedenou tabulku zjistíme, že priority operátorů jsou zvoleny velmi rozumně – odpovídají jak matematickým pravidlům (umocňování má nejvyšší prioritu, následuje násobení a dělení, naopak sčítání a odčítání jsou aritmetické operátory s nejnižší prioritou), tak i požadavkům z programátorské praxe, aby se zabránilo nutnosti používání zbytečných závorek například při psaní příkazů typu:
if x>10 and x<20 or x>110 and x<120 then … end.

Priorita Operátory
1 (nejvyšší) ^
2 not # unární minus (-)
3 * / %
4 + -
5 ..
6 == ~= < > <= >=
7 and
8 (nejnižší) or

6. Asociativní pole

V předchozí části tohoto seriálu jsme si popsali jednotlivé datové typy, se kterými můžeme v programovacím jazyku Lua pracovat. Mezi podporovanými datovými typy se nachází i asociativní pole, někdy také nazývané hashmapa. Jedná se o datovou strukturu, v níž jsou uloženy dvojice klíč–hodnota, kde klíčem může být libovolný typ kromě typu nil, hodnota může být libovolná (může se jednat i o další pole, funkci atd.). V případě, že za klíče jsou zvolena přirozená čísla, jsou asociativní pole ze sémantického hlediska rovnocenná klasicky chápaným indexovaným polím. Tento datový typ tak představuje základ jak pro tvorbu indexovaných polí, tak i seznamů, zásobníků, front, stromů, struktur typu záznam (jako klíče jsou v tomto případě použity řetězce) a dokonce i objektů (jako hodnoty uložené v asociativním poli lze totiž použít i funkce, které tak vystupují v roli metod). V následujících kapitolách si ukážeme základy práce s asociativním polem, podrobnější (a zajímavější) způsoby práce budou vysvětleny v navazující části tohoto seriálu.

7. Vytvoření asociativního pole

Existuje několik různých způsobů, pomocí nichž je možné vytvořit asociativní pole. Nejjednodušší způsob spočívá v konstrukci pole jediným příkazem, v němž jsou vypsány všechny ukládané dvojice klíč–hodnota:

pole={klic1="hodnota1", klic2="hodnota2"} 

Popř. je povolené za poslední dvojicí uvést čárku navíc. Tato čárka se může objevit například v automaticky generovaném kódu (mimochodem například populární formát JSON založený na JavaScriptu tuto čárku nepovoluje, což zbytečně komplikuje všechny generátory dat). Verze s nadbytečnou čárkou vypadá velmi podobně jako předchozí úryvek kódu:

pole={klic1="hodnota1", klic2="hodnota2",} 

V případě, že se má vytvořit „klasické“ pole s celočíselnými indexy, je možné specifikaci klíčů vynechat. V tomto případě překladač sám potřebné indexy doplní; ovšem musíme si dát pozor na to, že v Lua indexy začínají od jedničky, nikoli od nuly, jak je tomu v některých dalších programovacích jazycích! (důvod hledejme v historii tohoto jazyka, který byl určen zejména pro laiky a nikoli profesionální programátory). Alternativní konstrukce pole s automaticky generovanými klíči vypadá následovně:

pole={"hodnota1", "hodnota2", "hodnota3"} 

Oba dva způsoby je možné navzájem promíchat. V tomto případě se u prvků neobsahujících klíč budou automaticky dopočítávat indexy, a u prvků, jež klíč obsahují, se samozřejmě klíč do asociativního pole uloží v nezměněné podobě. Důležité je, že i explicitním zadáním celočíselného klíče se nemění hodnota interního čítače, pomocí kterého překladač asociativní pole konstruuje (jinými slovy – nemůžeme překladači jednoduše nařídit, aby například začal automaticky číslování od hodnoty 42). Vše možná osvětlí následující příklad, ve kterém se asociativní pole konstruuje s prvky, z nichž u některých je klíč uveden explicitně a u dalších se ponechává generování klíče (tj. v tomto případě indexu) na překladači. O výpis celého pole se stará smyčka typu foreach spolu s funkcí pairs, pomocí níž je možné iterovat celým asociativním polem:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4"}

for klic,hodnota in pairs(pole) do
    print(klic, hodnota)
end 

Výstup programu:

1       hodnota2
2       hodnota4
klic1   hodnota1
klic2   hodnota3 

Vzhledem k tomu, že klíč je v poli vždy jedinečný (může existovat maximálně jeden prvek s daným klíčem), může nastat situace, že se při konstrukci pole prvky navzájem přepíšou, jako tomu je v následujícím případě, kde je původní hodnota „hodnota1“ přepsána novým řetězcem „hodnotax“:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4", klic1="hodnotax", } 

CS24_early

8. Přístup k prvkům asociativního pole

Po zkonstruování asociativního pole je možné přistupovat k jeho jednotlivým prvkům pomocí zápisu, který je prakticky shodný ve většině současných programovacích jazyků: identifikátor_po­le[klíč]. Za identifikátor (jméno) asociativního pole se do hranatých závorek zapíše klíč, což může být libovolná hodnota (typicky číslo či řetězec), proměnná či výraz. V případě, že se v asociativním poli nachází prvek s daným klíčem, je hodnota tohoto prvku vrácena. Pokud prvek naopak nalezen není, vrátí se hodnota nil. Kromě tohoto způsobu zápisu nabízí Lua i alternativní způsob (syntaktický cukr), který se často používá v případech, kdy jsou asociativní pole použita ve funkci záznamu (record, struct) či objektu. Tento způsob se zapisuje následovně: identifikátor_po­le.klíč, tj. klíč zde není uveden v hranatých závorkách, ale za identifikátorem pole, od něhož je oddělen tečkou. Příklad použití:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4"}

print(pole["klic1"])
print(pole["klic2"])
print(pole["klic3"]) -- neexistujici prvek, vypise se "nil"
print(pole.klic1)
print(pole.klic2)
print(pole.klic3)    -- neexistujici prvek, vypise se "nil" 

Po spuštění programu se na standardní výstup vypíše:

hodnota1
hodnota3
nil
hodnota1
hodnota3
nil 

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.