Arrays, lists and maps
Store many values in arrays, lists and maps, walk them with FOR and FOREACH, and read constant tables with DATA and READ.
In one line#
aukimi has three containers for many values, plus a way to write constant tables in the program itself:
| Array | List | Map | |
|---|---|---|---|
| Declare | DIM a(10) or a AS integer[] | NewList l.i() | NewMap m.i() |
| Find a value by | its position: a(3) | the current element: l() | a text key: m("gold") |
| Grow | .insert(v), .length = n | AddElement(l()) | write a new key |
| Walk | FOR i = 0 TO a.length | FOREACH l() | FOREACH m() |
Assigning a container to another variable does not copy it. After
b = a, both names point to the same array, list or map, and a change through one shows through the other. Copy withCopyArray,CopyListorCopyMap.
Records, which group named fields, are on their own page: Records and methods.
DIM#
DIM <name>(<last index> [, <last index>…]) [AS <type>] [= [<value>, …]]
DIM <name>[<last index> [, <last index>…]] [AS <type>] [= [<value>, …]]
DIM scores(4)
scores(0) = 10
scores(4) = 50
Console(Str(scores.length)) ' 4, the last index
FOR i = 0 TO scores.length
Console(Str(scores(i))) ' 10, 0, 0, 0, 50
NEXT
The number is the last index, not the count.
DIM scores(4)makes five cells, numbered 0 to 4. This is the AGK, DarkBASIC and PureBasic rule, and not the C or JavaScript one.
- Every cell starts at 0, or at
""when the name ends in$. - A bound can be any expression:
DIM grid(width, height). - With
AS <type>, every write is converted to that type.AS integerdrops decimals,AS uint8wraps at 256. WithASa record type, every cell is a record. See Variables and types. DIMon a name that already exists makes a new array: the old values are lost. To resize and keep the values, assign.length(below).- A
DIMat the top of the program is visible inside every FUNCTION. ADIMinside a FUNCTION belongs to that function.
DIM names$(2)
names$(1) = "Ada"
DIM ages(2) AS integer
ages(0) = 3.7
DIM bytes(1) AS uint8
bytes(0) = 300
Console(names$(1) + " " + Str(ages(0)) + " " + Str(bytes(0))) ' Ada 3 44
⚠️ GLOBAL DIM and LOCAL DIM do not work reliably. With round brackets,
the next use of the array is refused as an unknown command. Inside a FUNCTION,
GLOBAL DIM does not make the array visible outside it. Write a plain DIM at
the top of the program instead.
Several dimensions, three spellings#
DIM grid(2, 3)
grid(1, 2) = 7
Console(Str(grid[1, 2])) ' 7
Console(Str(grid[1][2])) ' 7
Console(Str(grid.length)) ' 2, last index of the first dimension
Console(Str(grid[0].length)) ' 3, last index of the second dimension
a(i, j), a[i, j] and a[i][j] read and write the same cell. Round brackets
are the classic spelling, square brackets the AGK one.
⚠️ There is no bounds check. Reading past the last index gives no value at
all (Str shows undefined). Writing past it silently grows the array, and the
cells in between have no value either. Check your index against .length.
Typed arrays: AS <type>[]#
<name> AS <type>[]
<name> AS <type>[<last index> [, <last index>…]]
values AS integer[] = [1, 2, 3]
Console(Str(values.length)) ' 2
more AS integer[5] = [9]
Console(Str(more[0]) + " " + Str(more[5])) ' 9 0
empty AS string[]
Console(Str(empty.length)) ' -1, no cell at all
This is the same array as DIM … AS, written the AGK way. With [] and no
bound, the array starts empty. FUNCTION parameters and results use this form too,
see Functions.
Initialisers and list assignment#
= [ … ] fills the cells from index 0. It works on one-dimensional arrays only.
A list longer than the array grows it.
Later in the program, assigning a list writes from index 0 and keeps the other cells. It grows the array when needed and never shrinks it:
DIM a(4)
a = [7, 8]
Console(Str(a(0)) + Str(a(1)) + " " + Str(a.length)) ' 78 4Array members#
DIM a(2) = [30, 10, 20]
a.insert(5)
a.insert(99, 0)
Console(Str(a.length)) ' 4
a.remove()
a.remove(0)
a.sort()
Console(Str(a(0)) + " " + Str(a(1)) + " " + Str(a(2))) ' 10 20 30
Console(Str(a.find(20))) ' 1
Console(Str(a.find(42))) ' -1
| Member | What it does |
|---|---|
a.length | the last index: the number of cells minus one, -1 when empty |
a.length = n | resizes so that n is the last index; kept cells keep their values, new ones get the default |
a.insert(v) | adds v at the end |
a.insert(v, i) | inserts v at index i and shifts the rest |
a.remove() | removes the last cell |
a.remove(i) | removes cell i and shifts the rest |
a.find(v) | the index of the first cell equal to v, or -1 |
a.sort() | sorts in place, smallest first |
UNDIM a[] | empties the array (.length becomes -1) |
.lengthis the last index. A loop over every cell isFOR i = 0 TO a.length, and the number of cells isa.length + 1. This is the AGK rule. Coming from JavaScript, it is the trap.
.insert,.removeand.length = nare statements, on their own line. They do not return a value..finduses strict equality:a.find("2")does not find the number 2. It scans the whole array, which does not need to be sorted..sort()compares strings character by character, by code: every capital letter comes before every small one ("Zebra"before"apple"). An array of records is sorted on its first field.- The members work on arrays inside records:
bag.names.insert("Key"). UNDIMtakes square brackets.UNDIM a()compiles into a broken program:
DIM a(2)
UNDIM a()
Catalogue commands that take an array. SortArray(a, options) sorts with
options (#AK_ORDER_HIGH_TO_LOW for largest first), RandomizeArray(a) shuffles,
CopyArray(source, destination) copies, CompareArray(a, b) returns 1 when the
two hold the same values, and ArraySize(a) returns the last index like
.length. See the command reference.
DIM a(2) = [1, 2, 3]
DIM b(0)
CopyArray(a, b)
b(0) = 99
Console(Str(a(0)) + " " + Str(b.length)) ' 1 2, a is untouched, b was resizedLists#
NewList <name>.<type>()
NewList <name>()
A list holds values in order and has one current element. Most list work moves that cursor, then reads or writes the element under it.
NewList scores.i()
AddElement(scores())
scores() = 10
AddElement(scores())
scores() = 30
FirstElement(scores())
AddElement(scores())
scores() = 20
FOREACH scores()
Console(Str(ListIndex(scores())) + ": " + Str(scores())) ' 0: 10, 1: 20, 2: 30
NEXT scores()
Console(Str(ListSize(scores()))) ' 3
scores()is the current element. Read it, or assign it:scores() = 10. With no current element, the program stops with "BASIC list has no current element".AddElementinserts after the current element and makes the new one current. That is why 20 lands between 10 and 30 above.- The type follows the dot: a short suffix (
.i,.f,.d,.s,.q…), a full name (.integer,.string,.float64), or a record type (NewList foes.Foe(), see below). Without a type, the list holds integers. - Values are converted to the list's type.
.fis a 32-bit float, so0.1reads back as0.10000000149011612. Use.dfor full precision. - A record list gives its fields through the current element:
foes().hp = 30. - A name cannot be both a list and a map.
⚠️ The $ sigil does not declare a list. NewList names$() is refused.
Write the type after a dot:
NewList names$()
NewList names.s()
AddElement(names())
names() = "Ada"
Console(names()) ' Ada
⚠️ += on the current element fails when the program runs, and so does
INC scores(). Write scores() = scores() + 5.
FOREACH#
FOREACH <list>()
<statements>
NEXT [<list>()]
FOREACH walks a list from the first element to the last. Inside the loop,
list() is the element of this pass, and you can change it.
NewList a.i()
FOR k = 1 TO 5
AddElement(a())
a() = k
NEXT
FOREACH a()
IF Mod(a(), 2) = 0 THEN DeleteElement(a())
NEXT
FOREACH a()
Console(Str(a())) ' 1, 3, 5
NEXT
- Deleting the current element inside FOREACH is safe: the loop goes on with the next one.
EXITandCONTINUEwork as in any loop.- The parentheses are required:
FOREACH aandNEXT aare compile errors.NEXTalone is fine. - FOREACH works on maps (below) and on list or map fields of a record:
FOREACH bag.items(). - FOREACH does not work on arrays: use
FOR i = 0 TO a.length. - FOREACH moves the list's one cursor. A FOREACH nested over the same list, or a call that moves the cursor, changes where the outer loop is.
- PureBasic's
ForEach … Nextis the same thing: keywords ignore case.
The 25 list calls#
Every call takes the list with its parentheses, scores(). Positions count from
0.
| Call | What it does | Returns |
|---|---|---|
AddElement(l()) | adds an element after the current one (first place when there is none) and makes it current | 1 |
InsertElement(l()) | adds an element before the current one and makes it current | 1 |
DeleteElement(l() [, flags]) | deletes the current element; the one before becomes current. With flags = 1, deleting the first element makes the new first one current | 1 if an element is current afterwards, else 0 |
ClearList(l()) | removes every element | nothing |
FreeList(l()) | removes every element and frees the list; ListSize then returns -1 | nothing |
ListSize(l()) | the number of elements | a count, -1 after FreeList |
ListIndex(l()) | the position of the current element | a position, -1 when none |
ResetList(l()) | moves the cursor before the first element | nothing |
NextElement(l()) | moves to the next element | 1, or 0 past the end |
PreviousElement(l()) | moves to the previous element | 1, or 0 before the start |
FirstElement(l()) | moves to the first element | 1, or 0 when empty |
LastElement(l()) | moves to the last element | 1, or 0 when empty |
SelectElement(l(), i) | moves to position i | 1, or 0 when out of range |
ChangeCurrentElement(l(), i) | the same as SelectElement | 1 or 0 |
CopyList(source(), destination()) | replaces the destination with a copy of the source | 1, or 0 when the types differ |
CompareList(a(), b() [, flags]) | compares two lists element by element; #AK_MATCH_FOLD_CASE ignores case | 1 if equal, else 0 |
MergeLists(source(), destination() [, where]) | moves every element of the source into the destination; where is #AK_LIST_LAST (default), #AK_LIST_FIRST, or #AK_LIST_BEFORE / #AK_LIST_AFTER the destination's current element | nothing |
SplitList(source(), destination() [, keep]) | moves the current element and everything after it into the destination; with keep = 1, the current element stays | nothing |
SortList(l(), options [, start, end]) | sorts a list of numbers or strings; options #AK_ORDER_LOW_TO_HIGH, #AK_ORDER_HIGH_TO_LOW, #AK_ORDER_FOLD_CASE | nothing |
SortStructuredList(l(), "field", options [, start, end]) | sorts a record list on one field, named as text | nothing |
RandomizeList(l() [, start, end]) | shuffles the list, or the range | nothing |
PushListPosition(l()) | remembers the current element | nothing |
PopListPosition(l()) | returns to the remembered element | nothing |
MoveElement(l(), where [, i]) | moves the current element: #AK_LIST_FIRST, #AK_LIST_LAST, or #AK_LIST_BEFORE / #AK_LIST_AFTER the element at position i | nothing |
SwapElements(l(), i, j) | swaps the elements at positions i and j | nothing |
A wrong number of arguments is a compile error, for example "SortList(...)
expects 2-4 argument(s)". The sort options are required: SortList(l()) alone
is refused.
TYPE Foe
name AS string
hp AS integer
ENDTYPE
NewList foes.Foe()
AddElement(foes())
foes().name = "orc"
foes().hp = 30
AddElement(foes())
foes().name = "elf"
foes().hp = 10
SortStructuredList(foes(), "hp", #AK_ORDER_LOW_TO_HIGH)
FOREACH foes()
Console(foes().name + " " + Str(foes().hp)) ' elf 10, orc 30
NEXT
Coming from PureBasic. The names and most arguments match. Three calls take
a position where PureBasic takes an element pointer, because aukimi has no
element pointers: SwapElements, ChangeCurrentElement and the last argument
of MoveElement. SortStructuredList takes the field name as text, not an
offset and a type. AddElement returns 1, not the address of the element.
foes()\hp is written foes().hp.
Maps#
NewMap <name>.<type>([<slots>])
A map stores values under text keys. It also has a current element.
NewMap stock.i()
stock("apple") = 3
stock("pear") = 5
stock("apple") += 2
Console(Str(stock("apple"))) ' 5
Console(Str(MapSize(stock()))) ' 2
IF FindMapElement(stock(), "plum") = 0 THEN Console("no plum")
FOREACH stock()
Console(MapKey(stock()) + " = " + Str(stock())) ' apple = 5, pear = 5
NEXT
m("key") = vcreates the entry or replaces its value.+=and the other compound assignments work on an entry.- Reading
m("key")creates the entry when it is missing, with the default value. To test without creating, useFindMapElement. - Every access makes that entry the current one.
m()is the current entry: read it, or assign it. - Keys are text. A number is turned into text:
m(1)andm("1")are the same entry. Keys are case-sensitive:"A"and"a"are two entries. - FOREACH and
NextMapElementvisit entries in the order they were created. <slots>is a size hint kept for PureBasic programs. It must be a positive whole number and changes nothing else.- A record map gives its fields through the entry:
bag("sword").qty = 1.
The 13 map calls#
| Call | What it does | Returns |
|---|---|---|
AddMapElement(m(), key [, flags]) | creates the entry, or resets an existing one to the default value, and makes it current. flags must be 0 | 1 |
FindMapElement(m(), key) | makes the entry current if it exists. Creates nothing | 1 if found, else 0 |
DeleteMapElement(m() [, key]) | deletes the entry, or the current one | 1 if something was deleted, else 0 |
ClearMap(m()) | removes every entry | nothing |
FreeMap(m()) | removes every entry and frees the map; MapSize then returns -1 | nothing |
MapSize(m()) | the number of entries | a count, -1 after FreeMap |
MapKey(m()) | the key of the current entry | text, "" when none |
ResetMap(m()) | moves the cursor before the first entry | nothing |
NextMapElement(m()) | moves to the next entry | 1, or 0 past the end |
CopyMap(source(), destination()) | replaces the destination with a copy of the source | 1, or 0 when the types differ |
CompareMap(a(), b() [, flags]) | compares keys and values; #AK_MATCH_FOLD_CASE ignores case | 1 if equal, else 0 |
PushMapPosition(m()) | remembers the current entry | nothing |
PopMapPosition(m()) | returns to the remembered entry | nothing |
NewMap m.s()
m("x") = "1"
m("y") = "2"
ResetMap(m())
WHILE NextMapElement(m())
Console(MapKey(m()) + "=" + m()) ' x=1, y=2
WENDINC and DEC#
INC <target> [, <amount>]
DEC <target> [, <amount>]
n = 5
INC n
INC n, 10
DEC n, 3
Console(Str(n)) ' 13
DIM a(2)
INC a[1], 4
Console(Str(a[1])) ' 4
b AS uint8 = 255
INC b
Console(Str(b)) ' 0, a uint8 wraps
INCadds 1, or the amount.DECsubtracts.- The target is a variable, an array cell in square brackets
a[i], a fieldp.x, or a mix:ps[i].x. - On a typed variable, the result wraps like any write to that type.
- Under
OPTION EXPLICIT, the target must be declared first.
⚠️ Use square brackets for an array cell. INC a(1) compiles into a broken
program, and so do INC ps(1).x, INC l() and INC m("k"). Write INC a[1],
INC ps[1].x, l() = l() + 1, m("k") += 1.
DIM a(2)
INC a(1)Swapping two values#
There is no SWAP a, b statement. The line is refused:
a = 1
b = 2
SWAP a, b
Swap() with parentheses exists, but it does something else: it ends a frame,
like Sync(). See The game loop and tasks.
Swap two variables or two array cells through a third one. Swap two list
elements with SwapElements(l(), i, j).
a = 1
b = 2
temp = a
a = b
b = temp
Console(Str(a) + " " + Str(b)) ' 2 1DATA, READ and RESTORE#
DATASECTION
[<label>:]
DATA[.<type>] <literal> [, <literal>…]
ENDDATASECTION
READ[.<type>] <target>
RESTORE [<label>]
A DATASECTION holds constant values written in the program. READ takes them
one by one, in order. RESTORE moves the reading position.
RESTORE Levels
READ count
FOR i = 1 TO count
READ name$
READ.f speed#
Console(name$ + " " + Str(speed#)) ' Forest 1.5, Cave 2.25
NEXT
DATASECTION
Levels:
DATA 2
DATA.s "Forest"
DATA.f 1.5
DATA.s "Cave"
DATA.f 2.25
ENDDATASECTION
| Spelling | Accepted forms |
|---|---|
| Opening | DATASECTION or DATA SECTION |
| Closing | ENDDATASECTION, END DATASECTION or END DATA SECTION |
DATA types. Each DATA line has one type, written after a dot:
| Suffix | Holds |
|---|---|
none, .i (also .l, .q, .w, .b, .a, .u, .c) | numbers |
.f, .d | numbers |
.s | strings in double quotes |
DATAis only allowed inside aDATASECTION. The section is not run: it can sit at the top or at the bottom of the program.- Values must be literals. Numbers can be negative, decimal,
%1010,0c12,0x10or1.25e2.TRUEandFALSEcount as 1 and 0.DATA 1 + 2is a compile error, and so is a string in aDATAline without.s. - Values are read in the order written, across lines and labels.
READ. The kind to read comes from READ.<type>, or else from the target's
sigil: $ reads a string, # a decimal number, no sigil a whole number.
- The target can be a variable, an array cell (
a(1)ora[1]), or a field (p.x). - Reading a whole number from a decimal value drops the decimals: 2.9 gives 2.
- Reading a string where the next value is a number, or the reverse, stops the program: "BASIC READ type does not match DATA".
- Reading past the last value stops the program: "BASIC READ reached the end of DATA".
RESTORE. RESTORE alone goes back to the first value. RESTORE Label goes
to the first value after that label. Labels ignore case, and an unknown label is
a compile error. A data label is not a GOSUB label: you cannot call it.
Coming from another BASIC. AGK has no DATA. DarkBASIC reads untyped DATA
lines with no section. aukimi follows PureBasic's DataSection, with a typed
DATA line, and adds two shortcuts: an untyped DATA holds numbers, and READ
can take its type from the sigil.