Tableaux, listes et dictionnaires

Ranger de nombreuses valeurs dans des tableaux, des listes et des dictionnaires, les parcourir avec FOR et FOREACH, et lire des tables constantes avec DATA et READ.

En un mot#

aukimi a trois conteneurs pour de nombreuses valeurs, plus une façon d'écrire des tables constantes dans le programme lui-même :

TableauListeDictionnaire
DéclarerDIM a(10) ou a AS integer[]NewList l.i()NewMap m.i()
Trouver une valeur parsa position : a(3)l'élément courant : l()une clé texte : m("gold")
Agrandir.insert(v), .length = nAddElement(l())écrire une nouvelle clé
ParcourirFOR i = 0 TO a.lengthFOREACH l()FOREACH m()

Affecter un conteneur à une autre variable ne le copie pas. Après b = a, les deux noms désignent le même tableau, la même liste ou le même dictionnaire, et un changement par l'un se voit par l'autre. Copiez avec CopyArray, CopyList ou CopyMap.

Les enregistrements, qui regroupent des champs nommés, ont leur propre page : Enregistrements et méthodes.

DIM#

DIM <nom>(<dernier indice> [, <dernier indice>…]) [AS <type>] [= [<valeur>, …]]
DIM <nom>[<dernier indice> [, <dernier indice>…]] [AS <type>] [= [<valeur>, …]]
DIM scores(4)
scores(0) = 10
scores(4) = 50
Console(Str(scores.length))   ' 4, le dernier indice
FOR i = 0 TO scores.length
  Console(Str(scores(i)))   ' 10, 0, 0, 0, 50
NEXT

Le nombre est le dernier indice, pas le nombre de cases. DIM scores(4) crée cinq cases, numérotées de 0 à 4. C'est la règle d'AGK, de DarkBASIC et de PureBasic, pas celle du C ni du JavaScript.

  • Chaque case commence à 0, ou à "" quand le nom finit par $.
  • Une borne peut être n'importe quelle expression : DIM grid(width, height).
  • Avec AS <type>, chaque écriture est convertie dans ce type. AS integer supprime les décimales, AS uint8 repart de zéro à 256. Avec AS suivi d'un type d'enregistrement, chaque case est un enregistrement. Voir Variables et types.
  • Un DIM sur un nom qui existe déjà crée un nouveau tableau : les anciennes valeurs sont perdues. Pour redimensionner en gardant les valeurs, affectez .length (plus bas).
  • Un DIM en haut du programme est visible dans toutes les FUNCTION. Un DIM dans une FUNCTION appartient à cette fonction.
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 et LOCAL DIM ne sont pas fiables. Avec des parenthèses, l'usage suivant du tableau est refusé comme une commande inconnue. Dans une FUNCTION, GLOBAL DIM ne rend pas le tableau visible au-dehors. Écrivez plutôt un DIM simple en haut du programme.

Plusieurs dimensions, trois graphies#

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, dernier indice de la première dimension
Console(Str(grid[0].length))   ' 3, dernier indice de la seconde dimension

a(i, j), a[i, j] et a[i][j] lisent et écrivent la même case. Les parenthèses sont la graphie classique, les crochets celle d'AGK.

⚠️ Il n'y a pas de contrôle des bornes. Lire au-delà du dernier indice ne donne aucune valeur (Str affiche undefined). Écrire au-delà agrandit le tableau sans prévenir, et les cases intermédiaires n'ont pas de valeur non plus. Comparez votre indice à .length.

Les tableaux typés : AS <type>[]#

<nom> AS <type>[]
<nom> AS <type>[<dernier indice> [, <dernier indice>…]]
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, aucune case

C'est le même tableau que DIM … AS, écrit à la manière d'AGK. Avec [] sans borne, le tableau commence vide. Les paramètres et les résultats de FUNCTION utilisent aussi cette forme, voir Les fonctions.

Les initialiseurs et l'affectation d'une liste#

= [ … ] remplit les cases à partir de l'indice 0. Cela ne marche que sur un tableau à une dimension. Une liste plus longue que le tableau l'agrandit.

Plus loin dans le programme, affecter une liste écrit à partir de l'indice 0 et garde les autres cases. Le tableau grandit si besoin, et ne rétrécit jamais :

DIM a(4)
a = [7, 8]
Console(Str(a(0)) + Str(a(1)) + " " + Str(a.length))   ' 78 4

Les membres des tableaux#

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
MembreCe qu'il fait
a.lengthle dernier indice : le nombre de cases moins un, -1 quand le tableau est vide
a.length = nredimensionne pour que n soit le dernier indice ; les cases gardées gardent leur valeur, les nouvelles reçoivent la valeur par défaut
a.insert(v)ajoute v à la fin
a.insert(v, i)insère v à l'indice i et décale la suite
a.remove()retire la dernière case
a.remove(i)retire la case i et décale la suite
a.find(v)l'indice de la première case égale à v, ou -1
a.sort()trie sur place, du plus petit au plus grand
UNDIM a[]vide le tableau (.length passe à -1)

.length est le dernier indice. Une boucle sur toutes les cases s'écrit FOR i = 0 TO a.length, et le nombre de cases vaut a.length + 1. C'est la règle d'AGK. Pour qui vient du JavaScript, c'est le piège.

  • .insert, .remove et .length = n sont des instructions, seules sur leur ligne. Elles ne rendent pas de valeur.
  • .find utilise l'égalité stricte : a.find("2") ne trouve pas le nombre 2. Il parcourt tout le tableau, qui n'a pas besoin d'être trié.
  • .sort() compare les chaînes caractère par caractère, selon leur code : toutes les majuscules passent avant toutes les minuscules ("Zebra" avant "apple"). Un tableau d'enregistrements est trié sur son premier champ.
  • Les membres marchent sur les tableaux rangés dans un enregistrement : bag.names.insert("Key").
  • UNDIM prend des crochets. UNDIM a() compile en un programme cassé :
DIM a(2)
UNDIM a()

Les commandes du catalogue qui prennent un tableau. SortArray(a, options) trie avec des options (#AK_ORDER_HIGH_TO_LOW pour le plus grand d'abord), RandomizeArray(a) mélange, CopyArray(source, destination) copie, CompareArray(a, b) rend 1 quand les deux contiennent les mêmes valeurs, et ArraySize(a) rend le dernier indice comme .length. Voir la référence des commandes.

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 est intact, b a été redimensionné

Les listes#

NewList <nom>.<type>()
NewList <nom>()

Une liste range des valeurs dans l'ordre et a un seul élément courant. La plupart des opérations déplacent ce curseur, puis lisent ou écrivent l'élément qu'il désigne.

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() est l'élément courant. On le lit, ou on l'affecte : scores() = 10. Sans élément courant, le programme s'arrête sur « BASIC list has no current element ».
  • AddElement insère après l'élément courant et rend le nouveau courant. C'est pourquoi 20 arrive entre 10 et 30 ci-dessus.
  • Le type suit le point : un suffixe court (.i, .f, .d, .s, .q…), un nom complet (.integer, .string, .float64), ou un type d'enregistrement (NewList foes.Foe(), voir plus bas). Sans type, la liste contient des entiers.
  • Les valeurs sont converties dans le type de la liste. .f est un flottant 32 bits : 0.1 se relit 0.10000000149011612. Prenez .d pour la pleine précision.
  • Une liste d'enregistrements donne accès aux champs par l'élément courant : foes().hp = 30.
  • Un même nom ne peut pas être à la fois une liste et un dictionnaire.

⚠️ Le sigil $ ne déclare pas une liste. NewList names$() est refusé. Écrivez le type après un point :

NewList names$()
NewList names.s()
AddElement(names())
names() = "Ada"
Console(names())   ' Ada

⚠️ += sur l'élément courant échoue à l'exécution, tout comme INC scores(). Écrivez scores() = scores() + 5.

FOREACH#

FOREACH <liste>()
  <instructions>
NEXT [<liste>()]

FOREACH parcourt une liste du premier élément au dernier. Dans la boucle, liste() est l'élément du passage en cours, et on peut le modifier.

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
  • Supprimer l'élément courant dans un FOREACH est sans danger : la boucle continue avec le suivant.
  • EXIT et CONTINUE marchent comme dans toute boucle.
  • Les parenthèses sont obligatoires : FOREACH a et NEXT a sont des erreurs de compilation. NEXT seul convient.
  • FOREACH marche sur les dictionnaires (plus bas) et sur les champs liste ou dictionnaire d'un enregistrement : FOREACH bag.items().
  • FOREACH ne marche pas sur les tableaux : utilisez FOR i = 0 TO a.length.
  • FOREACH déplace l'unique curseur de la liste. Un FOREACH imbriqué sur la même liste, ou un appel qui déplace le curseur, change l'endroit où en est la boucle extérieure.
  • Le ForEach … Next de PureBasic est la même chose : les mots-clés ignorent la casse.

Les 25 appels de liste#

Chaque appel prend la liste avec ses parenthèses, scores(). Les positions comptent à partir de 0.

AppelCe qu'il faitRend
AddElement(l())ajoute un élément après le courant (en première place s'il n'y en a pas) et le rend courant1
InsertElement(l())ajoute un élément avant le courant et le rend courant1
DeleteElement(l() [, flags])supprime l'élément courant ; le précédent devient courant. Avec flags = 1, supprimer le premier élément rend courant le nouveau premier1 s'il reste un élément courant, sinon 0
ClearList(l())retire tous les élémentsrien
FreeList(l())retire tous les éléments et libère la liste ; ListSize rend alors -1rien
ListSize(l())le nombre d'élémentsun nombre, -1 après FreeList
ListIndex(l())la position de l'élément courantune position, -1 s'il n'y en a pas
ResetList(l())place le curseur avant le premier élémentrien
NextElement(l())passe à l'élément suivant1, ou 0 après la fin
PreviousElement(l())passe à l'élément précédent1, ou 0 avant le début
FirstElement(l())va au premier élément1, ou 0 si la liste est vide
LastElement(l())va au dernier élément1, ou 0 si la liste est vide
SelectElement(l(), i)va à la position i1, ou 0 hors limites
ChangeCurrentElement(l(), i)la même chose que SelectElement1 ou 0
CopyList(source(), destination())remplace la destination par une copie de la source1, ou 0 si les types diffèrent
CompareList(a(), b() [, flags])compare deux listes élément par élément ; #AK_MATCH_FOLD_CASE ignore la casse1 si égales, sinon 0
MergeLists(source(), destination() [, where])déplace tous les éléments de la source dans la destination ; where vaut #AK_LIST_LAST (par défaut), #AK_LIST_FIRST, ou #AK_LIST_BEFORE / #AK_LIST_AFTER l'élément courant de la destinationrien
SplitList(source(), destination() [, keep])déplace l'élément courant et tout ce qui le suit dans la destination ; avec keep = 1, l'élément courant resterien
SortList(l(), options [, start, end])trie une liste de nombres ou de chaînes ; options #AK_ORDER_LOW_TO_HIGH, #AK_ORDER_HIGH_TO_LOW, #AK_ORDER_FOLD_CASErien
SortStructuredList(l(), "field", options [, start, end])trie une liste d'enregistrements sur un champ, nommé en texterien
RandomizeList(l() [, start, end])mélange la liste, ou la plagerien
PushListPosition(l())mémorise l'élément courantrien
PopListPosition(l())revient à l'élément mémorisérien
MoveElement(l(), where [, i])déplace l'élément courant : #AK_LIST_FIRST, #AK_LIST_LAST, ou #AK_LIST_BEFORE / #AK_LIST_AFTER l'élément à la position irien
SwapElements(l(), i, j)échange les éléments aux positions i et jrien

Un mauvais nombre d'arguments est une erreur de compilation, par exemple « SortList(...) expects 2-4 argument(s) ». Les options de tri sont obligatoires : SortList(l()) seul est refusé.

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

Si vous venez de PureBasic. Les noms et la plupart des arguments correspondent. Trois appels prennent une position là où PureBasic prend un pointeur d'élément, parce qu'aukimi n'a pas de pointeurs d'élément : SwapElements, ChangeCurrentElement et le dernier argument de MoveElement. SortStructuredList prend le nom du champ en texte, pas un décalage et un type. AddElement rend 1, pas l'adresse de l'élément. foes()\hp s'écrit foes().hp.

Les dictionnaires#

NewMap <nom>.<type>([<slots>])

Un dictionnaire range des valeurs sous des clés texte. Il a lui aussi un élément courant.

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("clé") = v crée l'entrée ou remplace sa valeur. += et les autres affectations composées marchent sur une entrée.
  • Lire m("clé") crée l'entrée quand elle manque, avec la valeur par défaut. Pour tester sans créer, utilisez FindMapElement.
  • Chaque accès rend cette entrée courante. m() est l'entrée courante : on la lit, ou on l'affecte.
  • Les clés sont du texte. Un nombre est converti en texte : m(1) et m("1") sont la même entrée. Les clés distinguent la casse : "A" et "a" sont deux entrées.
  • FOREACH et NextMapElement visitent les entrées dans leur ordre de création.
  • <slots> est une indication de taille gardée pour les programmes PureBasic. Elle doit être un entier positif et ne change rien d'autre.
  • Un dictionnaire d'enregistrements donne accès aux champs par l'entrée : bag("sword").qty = 1.

Les 13 appels de dictionnaire#

AppelCe qu'il faitRend
AddMapElement(m(), key [, flags])crée l'entrée, ou remet une entrée existante à la valeur par défaut, et la rend courante. flags doit valoir 01
FindMapElement(m(), key)rend l'entrée courante si elle existe. Ne crée rien1 si trouvée, sinon 0
DeleteMapElement(m() [, key])supprime l'entrée, ou l'entrée courante1 si quelque chose a été supprimé, sinon 0
ClearMap(m())retire toutes les entréesrien
FreeMap(m())retire toutes les entrées et libère le dictionnaire ; MapSize rend alors -1rien
MapSize(m())le nombre d'entréesun nombre, -1 après FreeMap
MapKey(m())la clé de l'entrée courantedu texte, "" s'il n'y en a pas
ResetMap(m())place le curseur avant la première entréerien
NextMapElement(m())passe à l'entrée suivante1, ou 0 après la fin
CopyMap(source(), destination())remplace la destination par une copie de la source1, ou 0 si les types diffèrent
CompareMap(a(), b() [, flags])compare clés et valeurs ; #AK_MATCH_FOLD_CASE ignore la casse1 si égaux, sinon 0
PushMapPosition(m())mémorise l'entrée couranterien
PopMapPosition(m())revient à l'entrée mémoriséerien
NewMap m.s()
m("x") = "1"
m("y") = "2"
ResetMap(m())
WHILE NextMapElement(m())
  Console(MapKey(m()) + "=" + m())   ' x=1, y=2
WEND

INC et DEC#

INC <cible> [, <quantité>]
DEC <cible> [, <quantité>]
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, un uint8 repart de zéro
  • INC ajoute 1, ou la quantité. DEC retranche.
  • La cible est une variable, une case de tableau entre crochets a[i], un champ p.x, ou un mélange : ps[i].x.
  • Sur une variable typée, le résultat boucle comme toute écriture dans ce type.
  • Sous OPTION EXPLICIT, la cible doit d'abord être déclarée.

⚠️ Utilisez des crochets pour une case de tableau. INC a(1) compile en un programme cassé, tout comme INC ps(1).x, INC l() et INC m("k"). Écrivez INC a[1], INC ps[1].x, l() = l() + 1, m("k") += 1.

DIM a(2)
INC a(1)

Échanger deux valeurs#

Il n'y a pas d'instruction SWAP a, b. La ligne est refusée :

a = 1
b = 2
SWAP a, b

Swap() avec des parenthèses existe, mais fait autre chose : il termine une image, comme Sync(). Voir La boucle de jeu et les tâches.

Échangez deux variables ou deux cases de tableau par une troisième. Échangez deux éléments de liste avec SwapElements(l(), i, j).

a = 1
b = 2
temp = a
a = b
b = temp
Console(Str(a) + " " + Str(b))   ' 2 1

DATA, READ et RESTORE#

DATASECTION
  [<étiquette>:]
  DATA[.<type>] <littéral> [, <littéral>…]
ENDDATASECTION

READ[.<type>] <cible>
RESTORE [<étiquette>]

Une DATASECTION contient des valeurs constantes écrites dans le programme. READ les prend une à une, dans l'ordre. RESTORE déplace la position de lecture.

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
PartieGraphies acceptées
OuvertureDATASECTION ou DATA SECTION
FermetureENDDATASECTION, END DATASECTION ou END DATA SECTION

Les types de DATA. Chaque ligne DATA a un seul type, écrit après un point :

SuffixeContient
aucun, .i (aussi .l, .q, .w, .b, .a, .u, .c)des nombres
.f, .ddes nombres
.sdes chaînes entre guillemets doubles
  • DATA n'est permis que dans une DATASECTION. La section n'est pas exécutée : elle peut se trouver en haut ou en bas du programme.
  • Les valeurs doivent être des littéraux. Les nombres peuvent être négatifs, décimaux, %1010, 0c12, 0x10 ou 1.25e2. TRUE et FALSE comptent pour 1 et 0. DATA 1 + 2 est une erreur de compilation, tout comme une chaîne dans une ligne DATA sans .s.
  • Les valeurs sont lues dans l'ordre d'écriture, d'une ligne et d'une étiquette à l'autre.

READ. Le genre à lire vient de READ.<type>, ou à défaut du sigil de la cible : $ lit une chaîne, # un nombre décimal, sans sigil un nombre entier.

  • La cible peut être une variable, une case de tableau (a(1) ou a[1]), ou un champ (p.x).
  • Lire un entier dans une valeur décimale supprime les décimales : 2.9 donne 2.
  • Lire une chaîne quand la valeur suivante est un nombre, ou l'inverse, arrête le programme : « BASIC READ type does not match DATA ».
  • Lire au-delà de la dernière valeur arrête le programme : « BASIC READ reached the end of DATA ».

RESTORE. RESTORE seul revient à la première valeur. RESTORE Étiquette va à la première valeur qui suit cette étiquette. Les étiquettes ignorent la casse, et une étiquette inconnue est une erreur de compilation. Une étiquette de données n'est pas une étiquette de GOSUB : on ne peut pas l'appeler.

Si vous venez d'un autre BASIC. AGK n'a pas de DATA. DarkBASIC lit des lignes DATA non typées, sans section. aukimi suit la DataSection de PureBasic, avec une ligne DATA typée, et ajoute deux raccourcis : un DATA sans type contient des nombres, et READ peut prendre son type dans le sigil.