Az int egy előjeles, négy bájtos egész szám típus.
A másik két egész számtípus a short és a long. A short típus egy kétbájtos, előjeles egész szám, a short literált egy lezáró 'h' karakter jelzi. Például:
Hasonlóan, a long típus megfelel egy nyolcbájtos előjeles egész számnak, a long literált pedig egy lezáró 'j' karakter jelez:
A float típus az IEEE szabványnak megfelelő nyolcbájtos lebegőpontos szám, amit gyakran "double"-nek hívnak más programozási nyelvekben. Egy float literál egy opcionális előjel-karakterrel kezdődik, amelyet számjegyek követnek, található benne egy tizedespont, a végén pedig opcionálisan egy lezáró 'f' található:
A real típus egy négybájtos lebegőpontos szám. A real literálok jelölése számjegyekből, tizedespontból, és egy lezáró 'e' karakterből áll. Ezt a típust hívják 'float'-nak néhány másik programozási nyelvben.
Bit- és bájtértékek használatához.
A boolean típus egy bájt méretű, és egy bitnyi adatot tárol. Jelölése ennek a bitnek az értéke, lezárva egy 'b' karakterrel:
A byte típus egy bájtot foglal, és 8 bitnyi adatot tárol. A jelölése '0x'-szel kezdődik, és egy vagy két hexadecimális számjegy követi:
A bináris adatok kezelésében a q jobban hasonlít a C-re, mint annak saját leszármazottai hasonlítanak rá. Ugyanis a bináris típusokat előjel nélküli egészeknek vehetjük, és részt vehetnek matematikai kifejezésekben és összehasonlításokban más szám típusokkal. Nincs 'true' és 'false' kulcsszó, valamint nincsenek külön logikai operátorok se.
Két elemi karaktertípus van q-ban. Ezek a típusok jobban megfelelnek az SQL-beli CHAR és VARCHAR típusoknak, mint más programozási nyelvek karakter típusai.
Egy char egy darab ASCII karakternek felel meg, és egy bájton tárolódik. Ez megfelel az SQL-es CHAR-nak. A char literált egy karakterrel jelöljük, és idézőjelek veszik körül:
Néhány karaktert, csakúgy mint az idézőjelet, nem lehet rendesen megadni, hiszen speciális jelentéssel bírnak. Akárcsak C-ben, ezeket a karaktereket escape-elni kell egy '\' karakterrel.
A symbol karakterek szekvenciáját tárolja. Egy symbol literált egy '`' (visszafordított idézőjel (AltGr+7 magyar billentyűzeten)) karakter kezd. Q-körökben ezt a karaktert "back tick"-nek hívják.
A symbol felbonthatatlan, ami azt jelenti hogy az egyes karakterek amik alkotják, direkt módon nem hozzáférhetők.
Fontos: A symbol nem string. Látni fogjuk később, hogy van egy string-ekhez hasonló konstrukció q-ban, mégpedig a karakterekből álló lista. Bár egy char-lista közeli rokona a symbolnak, hangsúlyozzuk, hogy a symbol nem char-okból épül fel. Az `a symbol és az "a" karakter nem ugyanaz. A "q" karakter és a `kdb symbol mindketten elemi típusok elemi értékei!
Egyik nagy előnye a q-nak, hogy képes mind idősoros, mind relációs adatokat feldolgozni konzisztens és hatékony módon. Q kiterjeszti az alapvető SQL dátum és idő típusokat, hogy megkönnyítse az időbeli adatokon való műveletvégzést, amire SQL-ben minimális lehetőségünk van, más programozási nyelvekben pedig eléggé ügyetlenül van megoldva. Azokkal kezdünk, amelyek SQL-ben is jelen vannak, a többi típus q-ban az idő kisebb részegységeivel foglalkoznak (pl. hónap, perc, másodperc).
A date négy bájton van tárolva, a jelölése pedig yyyy.mm.dd, ahol yyyy felel meg az évnek, mm a hónapnak, dd pedig a napnak. A date értéke az eltelt napok számát tárolja 2000 január 1.-je óta.
A kezdő nullák a hónapoknál és napoknál szükségesek, elhagyásuk hibának számít.
A time típus négy bájton tárolódik, a jelölése pedig hh:mm:ss:uuu, ahol hh az óra (01-23), mm a perc, ss a másodperc, uuu pedig a milliszekundum. A time érték az eltelt milliszekundumokat tárolja éjféltől számítva.
A kezdőnullák itt is minden időegységnél szükségesek!
A datetime a date és time típusok kombinációja, szeparálva egy 'T'-vel, az ISO szabvány szerint.
A month típus négybájtos, a jelölése pedig yyyy.mm, egy lezáró 'm'-el a végén. A month érték az elmúlt hónapokat tárolja 2000 januárjától.
A minute típus négy bájtos, a jelölése pedig hh:mm. A minute érték az eltelt perceket tárolja éjféltől számítva.
A second típus négybájtos, a jelölése pedig hh:mm:ss. A second értéke az elmúlt másodperceket tartalmazza éjféltől.
Összetettebb adatok az elemi típusokból (atomok) épülnek fel, és listákból. Fontos, hogy alaposan megismerjük a listákat, hiszen szinte az összes q program magába foglalja listák feldolgozását. A koncepció egyszerű, de a bonyolultság gyorsan nőhet.
A lista egyszerűen egy rendezett adatgyűjtemény. Pontosabban, egy lista az egy rendezett gyűjteménye az elemi típusoknak, és más listáknak. Mivel ez a definíció rekurzív, kezdjük az elemi típusok listáival.
Egy általános listának a jelölése zárójelben történik, az elemeit felsorolva, pontosvesszővel elválasztva. Az olvashatóság kedvéért opcionálisan whitespace karaktereket használhatunk a pontosvesszők után.
Fontos: A definíció az elemek között sorrendet határoz meg (balról jobbra). Az (1;2) lista és a (2;1) lista különbözőek. Az SQL halmazokra alapul, amelyek így rendezetlenek. Ez a differencia további különbségekhez vezethet lekérdezéseknél q és SQL között. A listák rendezettsége egyszerűvé és hatékonnyá teszi az idősoros adatok feldolgozását q-ban, ami nehézkes és igen kevéssé hatékony SQL-ben.
A listákat értékül adhatjuk változóknak, akárcsak az elemi típusokat:
A lista elemszáma könnyen hozzáférhető a count függvénnyel:
A count egy int értékkel tér vissza, ami megegyezik a jobb oldalán lévő lista elemszámával:
Egy atom elemszáma mindig 1:
Egy egyszerű lista - ami egy egységes típusú, atomokból álló lista - megfelel a matematikában használatos vektornak (innentől a vektor szinonimája az egyszerű listának). Az ilyen listák speciálisan vannak kezelve q-ban. Egyszerűbb a jelölésük, kevesebb helyre van szükségük tároláskor, és gyorsabban lehet rajtuk műveleteket végezni, mint általános listákon. A jelölésük történhet úgy, mint az általános listáké, de ahol lehetséges, a q az általános listákat vektorrá konvertálja.
Bármelyik numerikus típusból álló egyszerű lista jelöléséből kihagyhatjuk a zárójeleket és pontosvesszőket. Így tehát a következő két definíció ekvivalens:
Hasonlóan jelöljük a real-ek és float-ok egyszerű listáit. Vegyük észre, hogy a q konzol elhagyja a tizedespontot, amikor attól jobbra csak nullák állnának, ám az érték ettől még nem lesz int:
Hogyha egyik értéknek sincs a tizedesponttól jobbra számjegye:
Vegyük észre a lezáró 'f'-et a lista végén, ez jelöli, hogy float-okból áll a lista.
Az egyszerűbb jelölése a bináis típusú listáknak szimplán egymás mellé helyezi az értékeket, majd lezárja a típust jelző karakterrel:
Byte-okból listáknál a típust jelző 0x a lista elején található:
Megjegyzés: A bitekből álló lista annyi bájtból áll, ahágy eleme van. A q nem sűrűti össze a bitekből álló listákat. A fenti bits lista öt bájt helyet foglal ennek megfelelően.
Az egyszerűsített jelölése a symbol listáknak elhagyja a zárójeleket és a pontosvesszőket, valamint nem lehet közöttük whitespace sem:
Az egyszerűbb jelölés a karakter-listáknak pontosan úgy néz ki, mint egy string a többi programozási nyelvben:
Különböző dátumtípusokkal definiált lista más viselkedéssel jár, mint egy különböző numerikus típusokból álló lista. Ebben az esetben a lista első elemének a típusát vesszük alapul, a többit pedig szűkítjük/bővítjük, hogy megfeleljen ennek.
Ahhoz, hogy kierőszakoljuk egy vegyes típusú listát dátum és idő típusokból, jelölnünk kell a típusazonosító karaktert:
Egyelemű és üres listák különleges elbánásban részesülnek.
Hasznos, hogy legyen egy üres listánk. Egy pár üres zárójel (maximum whitespace-t tartalmazhat) jelöli az üres listát:
Érdekes, ahogyan a q az egyelemű listákat kezeli. Mellesleg az ilyen listákat q-ban singleton-nak hívjuk. Egy singleton létrehozása egy jelölési problémával jár. Először is, egy lista amely csupán egyetlen atomot tartalmaz, különbözik egy atomtól. Hiszen ha van egy dolog egy dobozban, az nem ugyanaz mint a dolog a doboz nélkül. A következőket atomokként ismerjük fel:
A következőket pedig kételemű listaként:
Hogyan hozzunk létre listát, ami mindössze egyetlen elemet tartalmaz? Jó kérdés. A válasz pedig, hogy nincs szintaktikus módszer erre. Talán arra gondolhatunk, hogy pusztán egy darab elemet zárójelbe írunk, de ez nem működik, mivel az eredmény így is egy atom:
Az egyelemű listák készítésének a módja, hogy használjuk az enlist nevű függvényt, ami egy singleton listával tér vissza, ami azt az egy elemet tartalmazza, amit az enlist-nek megadtunk:
Megjegyzés: az outputban látható vessző a q magnyelvének, a k-nak egy operátora. q kifejezésben ezt nem használhatjuk, ezért van helyette az enlist függvény.
A következő kifejezés szintén egy singleton listát hoz létre:
A lista rendezve van, mégpedig balról jobbra. A lista első elemét 0-val indexeljük, a másodikat (ha van) 1-gyel, és így tovább. Egy n-elemű lista elemei 0-tól n-1-ig vannak indexelve.
Adott egy L nevű lista, az i-edik elemhez az L[i] kifejezéssel férhetünk hozzá:
Egy lista egy elemének értéket is adhatunk az indexén keresztül:
Fontos: Egyszerű listáknál (vektor) indexen keresztüli értékadásnál a típusoknak pontosan meg kell egyezniük. Továbbá ha egy általános listában értékadások miatt minden elem ugyanolyan típusuvá válik, a lista "leegyszerűsödik" és ezután nem tudunk bele más típusú elemet rakni:
Ha rossz típussal próbálunk indexelni, hibát kapunk:
Ha olyan indexet adunk meg, amelyik a lista határán kívül van, akkor az eredmény nem lesz error. Egy null-értéket kapunk vissza. Ha a lista egyszerű, akkor ez a null érték a listában lévő atomhoz tartozó null. Általános listák null-értéke 0n.
Egy üres index visszaadja az egész listát:
A dupla kettőspont (::) jelöli a null-elemet, ami explicit jelölést ad az üres indexre:
Megjegyzés: A null-elem típusa nem meghatározott (undefined), a típusa nem felel meg egyik normális elemnek sem a listában. Ezért a null-elemmel kierőszakolhatjuk általános lista létrehozását:
Listákat változókból is létrehozhatunk:
Listák összefűzése a vessző (,) operátorral történik. A vessző operátor jobb oldalán lévő listát hozzáfűzi a vessző bal oldalán lévő listának a végéhez:
Listákat egymásba ágyazhatunk egyszerűen úgy, hogy egy listának elemként egy másik listát adunk meg
Mélységen az egymásba ágyazott listák szintszámát értjük. Az atomok mélységeit 0-nak vesszük, az egyszerű listák mélysége pedig 1.
Az összetett listák jelölése is mutatja az egymásba ágyazásokat. A következő listának 2-es a mélysége, 3 eleme van, az első kettő atom, az utolsó pedig egy egyszerű lista:
A belső listát egyszerűbb alakban is írhatjuk:
Egymásba ágyazott listák indexelésénél is a [] operátort használjuk, méghozzá a C-hez hasonló módon:
Ezenkívül van még egy alternatív jelölés az egymásba ágyazott listák indexelésére. Itt az indexeket pontosvesszővel elválasztva adhatjuk meg:
Q-ban lehetőség van egy listának egyszerre több elemének is a lekérdezésére:
Az indexek lehetnek bármilyen sorrendben:
Egy index többször is szerepelhet:
Ez megmagyarázza, hogy miért kell pontosvesszőket beiktatnunk, amikor mélységekben indexelünk.
Indexnek használhatunk akár egy másik listát is:
Már az egyszerű listákkal történő indexelés is bemutatja, hogy milyen kifejezőereje és flexibilitása van a q-nak, de ettől még mélyebb vizekre is evezhetünk. Amikor egyszerű listákkal indexeltünk, akkor az eredmény lista egy új lista volt, amelynek az értékei az eredeti lista első szintjéről származtak, a lista alakja pedig megegyezett magával az indexszel. Igaz ami igaz, az eredmény listának az alakja meg kell hogy egyezzen az index-listának az alakjával. Ez megmagyarázza mi történik, mikor nem egyszerű listával indexelünk:
A listákról és indexelésükről további információk: itt.
A típuskényszerítés (cast) az a művelet, amellyel egy értéknek megváltoztathatjuk a típusát. A q ezt egy bináris operátor, a $ segítségével oldja meg. Az operátor első paramétere a típus, amelyre konvertálni akarunk, a második pedig a konvertálandó érték. A típust megadhatjuk háromféleképpen: vagy a nevét adjuk meg szimbólumként (pl. `long), vagy a számkódját short típusban (pl. 7h - vigyázat, más számtípussal nem működik), vagy a karakterkódját (pl. "j").
Ez a módszer nem működik szimbólumok és stringek esetén. Egy atomi értékből stringet csinálhatunk a string nevű függvénnyel:
Stringből szimbólumot képezhetünk úgy, hogy az üres szimbólumot használjuk típusnév helyett. Így létrehozhatunk pl. szóközt és ékezetes betűket tartalmazó szimbólumot is:
Végül stringből kiolvashatunk számot és egyéb típusokat is. Ehhez a karakteres típusnevet kell használnunk, csak nagybetűsen:
A típuskényszerítés akkor is hasznos lehet, ha egy egyszerű listába szeretnénk betenni egy olyan elemet, amelynek nem ismerjük a típusát. A type függvény egy q objektumról visszaadja típusának kódját. Atomi típus esetén negatív előjellel, egyszerű lista esetén pozitív előjellel kapjuk meg a típust. A kapott értéket felhasználhatjuk a $ operátorban is:
A szótár tulajdonképpen a lista általánosítása, és az alapja a tábláknak. A szótár egy hozzárendelés egy értelmezési tartomány-lista (továbbiakban domain-lista) és egy értéktartomány-lista (továbbiakban érték-lista) között. A két listának megegyező elemszámúnak kell lennie, és a domain-lista elemeinek páronként egyedinek illene lenniük, bár ez nem kötelező q-ban.
A szótár egy rendezett gyűjtemény kulcs-érték pároknak. Minden szótárnak 99h a típusa. Szótárakat a felkiáltójel (!) operátorral hozhatunk létre. A count függvénnyel pedig megkaphatjuk a szótárban lévő bejegyzések számát:
Egy szótár kulcsait megkaphatjuk a key függvénnyel:
Egy szótár értékeit megkaphatjuk a value függvénnyel:
A konzol a szótárat a következő formában jeleníti meg:
A cols függvény szintén visszaadja a domain-listát:
Megjegyzés: A szótárban lévő bejegyzések sorrendje számít, csakúgy mint a listák elemeinek a sorrendje is számít. Így aztán két olyan szótár, ami ugyanazokat a bejegyzéseket tartalmazza, viszont más sorrendben, nem egyenlőek egymással:
Egy adott kulcshoz tartozó értéket könnyedén visszakaphatunk egy szótárból. A szintaxis hasonló a listák indexeléséhez:
Csakúgy mint listák esetében, ha olyan kulccsal keresünk, amihez nincs bejegyzés a szótárban, akkor az eredmény nem error, hanem egy típusnak megfelelő null-elem:
Szintén működik a listáknál látott multi-indexelés is:
A szótár a listának egy általánosítása, ahol az elemek indexelésének lehetőség kiterjesztettük. Másrészt, egy szótárat nem indexelhetünk pozíció szerint, hogy visszakapjunk egy egész kulcs => érték bejegyzést. Az ilyen próbálkozás hibával jár.
Persze természetesen készíthetünk olyan listát, aminél a hozzárendelés emulálja a listák indexelését:
Amikor ezesetben megkérjük a q-t, hogy hasonlítsa össze a két entitás egyenlőségét, akkor ő pozíció alapú indexelést használ. Ezután ellenőrzi egyesével az elemeket:
A fenti kimenet azt jelenti, hogy a 0., 1. és 2. elem is megegyezik. Persze az így definiált szótár nem ugyanaz mint a lista:
Habár az elemek elérése egy "lista-stílusú" szótárban jelölésben azonos a listák elem-indexeléséhez, a kettő nem ugyanaz. Az elem-indexelés egy pozícionális eltolás, míg a szótárban az elem elérése egy keresés. Különbözőképpen vannak implementálva a felszín alatt.
Ahogy korábban megjegyeztük, a q nem kényszeríti ki azt, hogy a szótárak domain-listája páronként különböző elemekből álljon. Hogyha egy kulcs többször is szerepel, akkor a keresés csupán egy elemet fog visszaadni, mégpedig az elsőt az érték-listából:
Megjegyzés: A fordított visszakeresés megfelelően működik egy nem egyedi domain-listán:
Nem kötelező, hogy a szótár érték-listája atomokból álljon. Lehet akár egy általános lista is, ami beágyazott listákat tartalmaz:
Megjegyzés: Ha a domain-lista elemeinek az alakja különbözik, akkor a keresés nem működik megfelelően:
A megfigyelt viselkedés azt mutatja, hogy a kulcs általi keresés csődöt mond az első olyan kulcsnál, amelyik alakja különbözik a legelső kulcstól.
Ahogy listáknál, az elemek a szótárban is módosíthatóak index általi értékadással:
Fontos: A listákkal ellentétben, a szótárak bővíthetőek index általi értékadással:
Tehát index általi értékadásnál, ha már van ilyen kulcsunk, akkor update-elünk, különben insert-elünk a szótárba. Ezt röviden upsert-nek hívjuk. Mivel a táblák a könyvtárakra épülnek, ez az upsert viselkedés a táblákra is igaz lesz.
Listáknál a kérdőjel (?) operátort használtuk arra, hogy megkeressük egy adott érték indexét. Ez a módszer a könyvtárakkal is működik:
Ha olyan elemet keresünk, amelyik nincs az érték-listában, akkor az eredmény egy típusnak megfelelő null-érték lesz. Egyszerű listáknál ez megfelel a lista típusához tartozó null-értéknek. Általános listáknál a null-érték 0N.
A bináris törlő operátor (_) eredménye egy olyan szótár, melyből töröltük a megadott kulcshoz tartozó bejegyzést.
Megjegyzés: Whitespace szükséges a _ baloldalára, ha a baloldali operandus egy változó:Ha több elemet akarunk törölni egyszerre, akkor szintén az aláhúzás (_) operátort kell használnunk, ám ez esetben a jobboldali operandus a szótár, a baloldali pedig egy lista, mely a törlendő kulcsokat tartalmazza:
A szótárakról bővebb leírás: itt.
A táblák a kdb+ adatbázis alapjai. A tábla egy oszlopokból álló gyűjtemény, amely szótárként van implementálva. Következésképpen a q táblák oszlop-orientáltak, ellentétben a sor-orientált relációs adatbázisokkal. Továbbá az oszlop értékei egy rendezett listát alkotnak, szemben az SQL-lel, ahol a sorok sorrendje nem definiált. Ez a tény - hogy a q táblák rendezett oszloplisták - teszik a kdb+ adatbázist nagyon hatékonnyá mind az adattárolás, a visszakeresés, valamint az adatmanipuláció műveleteit rendezett adatokon. Egy fontos példa erre, mikor fontos az adatok időrendisége.
A kdb+ képes kezelni mind relációs, mind idősoros adatot a q táblákon keresztül. Nincs elkülönült adatdefiniáló nyelv, és nincsenek külön tárolt függvények. Csupán q táblák, kifejezések és függvények.
A táblák a szótárakból épülnek fel, ezért a szótárakról szóló részt mindenképp el kell olvasni előbb.
A tábla nem más, mint egy oszlop-szótárnak az elforgatása (vagyis transzponálása). Minden tábla a 98h típus-azonosítóval rendelkezik. Például:
Figyeljük meg, hogy egy tábla oszlopainak és sorainak a megjelenítése megfelel a szótár-féle megjelenítés transzponáltjának, még ha a belső adatszerkezet ugyanaz is.
Táblát nem csak oszlop-szótár flip-pelésével hozhatunk létre, hanem van erre saját szintaxis is:
Itt c1 egy azonosító, ami egy oszlopnevet tartalmaz, L1 pedig a hozzátartozó lista, ami az oszlop értékeit tartalmazza. A listáknak azonos számosságúaknak kell lenniük. A szögletes zárójel célja a kulcs meghatározása. Lehet oszlopnevek nélkül is definiálni táblát, ekkor az oszlopok rendre az x, x1, x2... neveket kapják. Példa a korábbi t tábla definiálására ilyen formában:
t ilyetén módon történő definiálása egyértelműen tisztább és érthetőbb, mint egy oszlop-szótár transzponálása.
Az oszlopokat váltózókban is tárolhatjuk, ami hasznos lehet programozási szempontból:
Megjegyzés: Amikor az összes Li lista singleton, akkor enlist-elni kell őket.
Megjegyzés: Ha legalább egy oszlop lista, és a többi oszlop közül egy vagy több atom, akkor minden atomi oszlop kiterjesztődik listának, aminek a számossága megegyezik a többi oszlopéval:
Az oszlopnevek kinyerhetőek egy táblából a cols függvényt használva:
A pont (.) operátort használva lehetőség van egy tábla oszlopát visszakapnunk. Például egy t tábla c oszlopát a t.c kifejezéssel is ki tudjuk nyerni:
A meta függvénnyel kinyerhetjük egy tábla összes metaadatát. Az eredmény egy olyan tábla, amelynek annyi sora van, ahány oszlopa t-nek volt. A tábla kulcsa a c oszlop, ez tartalmazza a tábla oszlopneveit. A t oszlop tartalmazza típust jelző szimbólumot az adott oszlophoz. Az f oszlop a külső kulcsokra vonatkozik. Az a oszlop tartalmaz bármilyen attribútumot, ami az adott oszlopra vonatkozik:
A count függvény visszatér egy tábla sorainak a számával:
Egy sort egyszerűen visszakaphatunk a szögletes zárójel operátort használva:
Ahogy az előző szekcióban láthattuk, egy táblát lehet definiálni és feltölteni egy külön erre való szintaxissal:
Ezt a gyakorlatban ritkán használjuk, ugyanis vagy túlságosan sok értéket kell felvenni a táblába, vagy pedig később akarjuk azt feltölteni. Ilyen körülmények között hasznos létrehozni egy üres táblát, majd feltölteni azt elemekkel később. A következő példában az üres zárójelek az üres listát jelölik:
A táblát később feltölthetjük értékekkel, például egy fájlból kiolvasva azokat.
Amikor egy táblát hozunk létre a fenti módon, az oszlopok általános típusú elemeket tartalmazó üres listák. Így bármilyen adattal fel lehet őket tölteni. Az oszlop típusát aztán az első behelyezett elem fogja meghatározni, az azt követő elemeknek ugyanolyan típusúaknak kell lenniük.
Lehetőség van azonban egy üres tábla létrehozásakor egyből meghatározni az oszlopok típusát. Ilyenkor csupán egy null-listát kell megadni a megfelelő típussal:
A kulcsos tábla még egy fokkal bonyolultabb adatszerkezet, mint a hagyományos tábla. Egy olyan szótár, amelynek a kulcs- és értéktartománya is tábla. Az egyszerűsített táblaszintaxisban a szögletes zárójelek között megadhatunk kulcsoszlopokat, így egyszerűen készíthetünk kulcsos táblát:
Kulcsos táblából indexeléssel kikereshetjük egy adott kulcshoz tartozó értéket.
Kulcsos táblához létrehozhatunk idegen kulcsot is. Ehhez a tábla nevét kell használnunk típus helyett egy oszlopnál. Az idegen kulcsoknak a q-sql lekérdezésekben van szerepük.
Bővebb információk a táblákról: itt.