Create: Computing AgesDoc Brass
Bibliothèques

table

Listes et tables : ajouter, retirer, trier, joindre, copier et chercher.

Tous les ordinateurs

Une table range des valeurs sous des clés. On s'en sert de deux façons, souvent mélangées dans la même table :

  • une liste : des valeurs aux positions 1, 2, 3... {"fer", "or", "cuivre"}. Sa longueur est #t.
  • un enregistrement : des valeurs sous des noms. {nom = "Lingot de fer", nombre = 64}, lues avec t.nom ou t["nom"].

La bibliothèque table travaille surtout sur les listes : table.insert, table.remove, table.sort et table.concat ne regardent que les positions 1 à #t. Le guide Tables explique les tables depuis le début.

Brass
local commandes = {"fer", "or"}
table.insert(commandes, "cuivre")
table.insert(commandes, 1, "charbon")
print(table.concat(commandes, ", "))
local premiere = table.remove(commandes, 1)
print(premiere, #commandes)
table.sort(commandes)
print(table.concat(commandes, " "))
Écran
charbon, fer, or, cuivre
charbon 3
cuivre fer or

Une table se partage, elle ne se copie pas. local b = a donne un deuxième nom à la même table : un changement fait par b se voit par a. Utilisez table.copy pour obtenir une table séparée. De même, == dit si deux noms désignent la même table, pas si deux tables contiennent la même chose.

**La longueur #t compte la partie liste : les positions 1, 2, 3... remplies l'une après l'autre. Une table qui n'a que des clés nommées a une longueur de 0. Mettre la dernière** valeur à nil raccourcit la liste ; en mettre une au milieu à nil laisse un trou que # compte toujours, mais où ipairs s'arrête. Une valeur loin après la fin (t[10] dans une liste de 3) ne fait pas partie de la liste tant que le vide n'est pas comblé.

Brass
local t = {"a", "b", "c"}
t[2] = nil
print(#t)
t[3] = nil
print(#t)
t[10] = "j"
print(#t)
Écran
3
1
1

Gardez vos listes sans trous : retirez les valeurs avec table.remove, qui referme l'espace, plutôt qu'en les mettant à nil.

Note

Chaque entrée d'une table prend 2 cellules de mémoire, plus 4 pour la table elle-même (voir Limites). Brass n'a ni table.unpack, ni table.pack, ni table.move, ni métatables.

Fonctions
table.insert(t, [pos,] value)Ajoute une valeur à la fin d'une liste, ou à une position donnée.
table.remove(t [, pos])Retire une valeur d'une liste et la renvoie : la dernière, ou celle de la position pos.
table.sort(t [, less])Trie une liste sur place : le plus petit d'abord, ou dans l'ordre donné par une fonction.
table.concat(t [, sep [, i [, j]]])Joint les valeurs d'une liste en un seul texte, avec sep entre elles.
table.contains(t, value)Vrai si la valeur se trouve quelque part dans la table.
table.keys(t)La liste des clés d'une table.
table.copy(t)Une copie simple d'une table : une nouvelle table avec les mêmes clés et valeurs.

Ajouter et retirer

#

table.insert(t, [pos,] value)

⚙ coût 1 par 8 valeurs, avec une position

Ajoute une valeur à la fin d'une liste, ou à une position donnée.

Paramètres
t table
la liste
pos number facultatif
la position où va la valeur, de 1 à #t + 1 (la fin si elle est omise)
value any
la valeur à ajouter

Avec deux arguments, la valeur va à la fin, en position #t + 1. Avec trois, elle va en pos, et les valeurs à partir de pos remontent d'une place. Une file de commandes de trains, avec une commande urgente qui passe devant :

Brass
local attente = {}
table.insert(attente, "Fer vers le dépôt A")
table.insert(attente, "Charbon vers la fonderie")
table.insert(attente, 1, "URGENT : vivres pour la base")
for i, commande in ipairs(attente) do
  print(i, commande)
end
Écran
1   URGENT : vivres pour la base
2   Fer vers le dépôt A
3   Charbon vers la fonderie
  • pos doit être un entier de 1 à #t + 1. Sinon le programme s'arrête avec bad argument #2 to 'insert' (position out of bounds).
  • Une valeur nil n'ajoute rien : une liste ne peut pas contenir nil, sa longueur ne change pas.
  • Avec seulement la table, le programme s'arrête avec wrong number of arguments to 'insert'.
  • t[#t + 1] = valeur fait la même chose que table.insert(t, valeur), sans le coût d'un appel.

Ajouter à la fin ne coûte presque rien. Insérer à une position déplace les valeurs qui suivent, ce qui coûte une instruction par tranche de 8 valeurs de la liste : sur une liste de milliers de valeurs, préférez ajouter à la fin.

Voir aussi table.remove()

#

table.remove(t [, pos])

→ any⚙ coût 1 par 8 valeurs déplacées

Retire une valeur d'une liste et la renvoie : la dernière, ou celle de la position pos.

Paramètres
t table
la liste
pos number facultatif
la position à retirer, de 1 à #t (la dernière si elle est omise)
Renvoie
any
la valeur retirée, nil si la liste était vide

Les valeurs après pos descendent d'une place : la liste ne garde aucun trou. Retirer la première valeur d'une file donne « premier arrivé, premier servi » ; retirer la dernière donne une pile (dernier arrivé, premier sorti).

Brass
local attente = {"Fer vers le dépôt A", "Charbon vers la fonderie", "Vivres pour la base"}
while #attente > 0 do
  local commande = table.remove(attente, 1)
  print("Départ : " .. commande)
end
print(table.remove(attente))
Écran
Départ : Fer vers le dépôt A
Départ : Charbon vers la fonderie
Départ : Vivres pour la base
nil
Une liste vide

Sans position, table.remove d'une liste vide renvoie nil. Avec une position, c'est une erreur : table.remove(attente, 1) sur une file vide s'arrête avec bad argument #2 to 'remove' (position out of bounds). Vérifiez d'abord #attente > 0, comme ci-dessus.

Retirer en parcourant une liste. En avançant, on saute des valeurs : quand la valeur en i part, la suivante glisse en position i, et la boucle passe à i + 1. Ici, un "fini" survit :

Brass
local liste = {"fini", "fini", "à faire"}
for i, etat in ipairs(liste) do
  if etat == "fini" then
    table.remove(liste, i)
  end
end
print(table.concat(liste, " "))
Écran
fini à faire

Parcourez plutôt la liste à l'envers : retirer une valeur ne déplace que celles que vous avez déjà vues.

Brass
local taches = {
  {nom = "presser des engrenages", finie = true},
  {nom = "mélanger du laiton", finie = false},
  {nom = "scier des planches", finie = true},
  {nom = "remplir les réservoirs", finie = false},
}
for i = #taches, 1, -1 do
  if taches[i].finie then
    table.remove(taches, i)
  end
end
for _, tache in ipairs(taches) do
  print(tache.nom)
end
Écran
mélanger du laiton
remplir les réservoirs

Voir aussi table.insert()

Trier

#

table.sort(t [, less])

⚙ coût n × log2(n) sans less

Trie une liste sur place : le plus petit d'abord, ou dans l'ordre donné par une fonction.

Paramètres
t table
la liste à trier, modifiée sur place
less function facultatif
less(a, b) renvoie true quand a doit passer avant b

La liste elle-même est modifiée, et rien n'est renvoyé. Sans fonction, les nombres vont du plus petit au plus grand, et les textes suivent l'ordre des codes de leurs caractères : les majuscules avant les minuscules, et les chiffres un par un ("10" avant "9").

Brass
local vitesses = {64, 16, 256, 32}
table.sort(vitesses)
print(table.concat(vitesses, " "))
local noms = {"zinc", "Fer", "cuivre", "Andésite"}
table.sort(noms)
print(table.concat(noms, " "))
Écran
16 32 64 256
Andésite Fer cuivre zinc

Les lettres accentuées ont des codes plus grands que toutes les lettres sans accent : "étain" se range après "zinc".

Avec une fonction, c'est vous qui décidez de l'ordre. less(a, b) reçoit deux valeurs de la liste et renvoie true quand a doit passer avant b, et false sinon, y compris quand elles sont égales. Les valeurs que la fonction juge égales gardent l'ordre qu'elles avaient (le tri est stable).

Brass
local vitesses = {64, 16, 256, 32}
table.sort(vitesses, function(a, b) return a > b end)
print(table.concat(vitesses, " "))
local noms = {"zinc", "Fer", "cuivre", "Andésite"}
table.sort(noms, function(a, b) return a:lower() < b:lower() end)
print(table.concat(noms, " "))
Écran
256 64 32 16
Andésite cuivre Fer zinc
Attention

Écrivez < ou > dans une fonction de tri, jamais <= ou >=. Brass ne s'arrête pas pour autant, mais les valeurs égales sortent alors dans l'ordre inverse, et un tri relancé continue de les échanger.

Les 5 objets les plus nombreux d'un coffre à objet renforcé : triez les enregistrements selon leur champ nombre, le plus grand d'abord, et affichez les cinq premiers. Le laiton et le fer ont le même nombre : ils gardent leur ordre de départ.

Brass
local coffre = {
  {nom = "Pierre taillée", nombre = 2304},
  {nom = "Lingot de laiton", nombre = 412},
  {nom = "Pépite d'or", nombre = 37},
  {nom = "Alliage d'andésite", nombre = 980},
  {nom = "Lingot de cuivre", nombre = 1290},
  {nom = "Lingot de zinc", nombre = 96},
  {nom = "Lingot de fer", nombre = 412},
}
table.sort(coffre, function(a, b)
  return a.nombre > b.nombre
end)
for i = 1, math.min(5, #coffre) do
  print(string.format("%d. %-18s %5d", i, coffre[i].nom, coffre[i].nombre))
end
Écran
1. Pierre taillée      2304
2. Lingot de cuivre    1290
3. Alliage d'andésite   980
4. Lingot de laiton     412
5. Lingot de fer        412

Pour départager les égalités, comparez un second champ quand le premier est égal. Avec cette fonction, le Lingot de fer passe avant le Lingot de laiton :

Brass
table.sort(coffre, function(a, b)
  if a.nombre ~= b.nombre then
    return a.nombre > b.nombre
  end
  return a.nom < b.nom
end)

Erreurs. Sans fonction, les valeurs doivent être toutes des nombres ou toutes des textes : un mélange s'arrête sur attempt to compare number with string (ou string with number), et des tables sur attempt to compare two table values : triez les tables d'enregistrements avec une fonction. Un trou dans la liste s'arrête sur attempt to compare nil with number.

Coût. Sans fonction, le tri est facturé environ n × log2(n) instructions d'un coup pour n valeurs : 10 000 pour 1000 nombres, bien moins d'une seconde sur un Micro-ordinateur (1200 instructions par tick), beaucoup plus sur un Calculateur à tubes (20 par tick). Avec une fonction, chaque comparaison est un appel à celle-ci, de quelques instructions, soit environ n × log2(n) appels. Dans les deux cas, le tri s'étale sur autant de ticks qu'il faut, comme n'importe quel code, et ne bloque jamais le serveur.

Voir aussi table.keys()

Joindre et chercher

#

table.concat(t [, sep [, i [, j]]])

→ string⚙ coût 1 par 16 caractères

Joint les valeurs d'une liste en un seul texte, avec sep entre elles.

Paramètres
t table
une liste de textes et de nombres
sep string facultatif
le texte placé entre les valeurs (rien s'il est omis)
i number facultatif
la première position (1 si elle est omise)
j number facultatif
la dernière position (#t si elle est omise)
Renvoie
string
les valeurs réunies en un seul texte
Brass
local trajet = {"Mine", "Fonderie", "Dépôt"}
print(table.concat(trajet, " -> "))
print(table.concat({64, 32, 16}, "+") .. " = 112")
print(table.concat(trajet, ", ", 2, 3))
print("[" .. table.concat({}) .. "]")
Écran
Mine -> Fonderie -> Dépôt
64+32+16 = 112
Fonderie, Dépôt
[]

Les valeurs doivent être des textes ou des nombres (les nombres s'écrivent comme avec tostring). Tout le reste, true, une table, ou un trou dans la liste, s'arrête sur invalid value (at index 2) in table for 'concat' : convertissez d'abord les valeurs avec tostring. Quand i est après j, le résultat est "".

Pour construire un long texte morceau par morceau, rangez les morceaux dans une liste et joignez-les une seule fois à la fin : c'est bien moins cher que s = s .. morceau dans une boucle, qui fabrique un nouveau texte à chaque tour. Le résultat est limité à 65536 caractères (string too long).

Voir aussi string.split()

#

table.contains(t, value)

→ boolean⚙ coût 1 par 8 valeurs

Vrai si la valeur se trouve quelque part dans la table.

Paramètres
t table
la table où chercher
value any
la valeur cherchée
Renvoie
boolean
true si l'une des valeurs de t est égale à value

Elle regarde les valeurs de la table, partie liste comme clés nommées, jamais les clés. Les valeurs sont comparées comme avec == : les nombres et les textes par leur contenu, les tables par identité (une autre table au contenu identique n'est pas trouvée). nil n'est jamais trouvé.

Brass
local combustibles = {"minecraft:coal", "minecraft:charcoal", "minecraft:blaze_rod"}
print(table.contains(combustibles, "minecraft:coal"))
print(table.contains(combustibles, "minecraft:stick"))
print(table.contains({vitesse = 64}, 64), table.contains({vitesse = 64}, "vitesse"))
Écran
true
false
true    false

table.contains parcourt toute la table à chaque appel. Pour tester beaucoup de valeurs contre une longue liste, construisez une fois pour toutes un ensemble, avec les valeurs comme clés, et consultez-le directement :

Brass
local combustibles = {"minecraft:coal", "minecraft:charcoal", "minecraft:blaze_rod"}
local est_combustible = {}
for _, id in ipairs(combustibles) do
  est_combustible[id] = true
end
print(est_combustible["minecraft:coal"], est_combustible["minecraft:stick"])
Écran
true    nil

Voir aussi table.keys()

#

table.keys(t)

→ table

La liste des clés d'une table.

Paramètres
t table
n'importe quelle table
Renvoie
table
une nouvelle liste de ses clés

L'ordre est celui de pairs : les positions 1, 2, 3... d'abord, puis les autres clés dans l'ordre où elles ont été ajoutées. Le résultat est une nouvelle liste : #table.keys(t) compte donc les entrées de n'importe quelle table, là où #t ne compte que sa partie liste. Triez les clés pour un affichage alphabétique :

Brass
local stock = {fer = 1200, cuivre = 640, zinc = 96}
local noms = table.keys(stock)
print(#stock, #noms)
table.sort(noms)
for _, nom in ipairs(noms) do
  print(nom, stock[nom])
end
Écran
0   3
cuivre  640
fer 1200
zinc    96

Des clés de sortes différentes (nombres et textes) ne se trient pas ensemble sans fonction.

Voir aussi pairs() table.sort()

Copier

#

table.copy(t)

→ table

Une copie simple d'une table : une nouvelle table avec les mêmes clés et valeurs.

Paramètres
t table
la table à copier
Renvoie
table
une nouvelle table avec les mêmes clés et valeurs

Servez-vous-en pour garder une sauvegarde avant de modifier une table, ou pour confier une table à du code sans le laisser modifier la vôtre. La copie contient la partie liste et les clés nommées, dans le même ordre.

Brass
local recette = {"fer", "fer", "bâton"}
local meme = recette
local sauvegarde = table.copy(recette)
recette[3] = "or"
print(meme[3], sauvegarde[3])
Écran
or  bâton

« Simple » veut dire sur un seul niveau : une table rangée à l'intérieur n'est pas copiée, les deux tables la partagent. Pour une copie complète, copiez aussi les tables intérieures :

Brass
local function copie_profonde(t)
  local copie = {}
  for k, v in pairs(t) do
    if type(v) == "table" then
      copie[k] = copie_profonde(v)
    else
      copie[k] = v
    end
  end
  return copie
end

local gare = {nom = "Nord", trains = {"T1", "T2"}}
local simple = table.copy(gare)
local profonde = copie_profonde(gare)
table.insert(gare.trains, "T3")
print(#simple.trains, #profonde.trains)
Écran
3   2

Voir aussi table.keys()

Motifs courants

Des totaux par objet. Un inventaire liste ses emplacements un par un (inventory.list()), et le même objet peut remplir plusieurs emplacements. Additionnez-les dans une table dont les clés sont les objets :

Brass
local cases = {
  {name = "minecraft:iron_ingot", count = 64},
  {name = "minecraft:coal", count = 12},
  {name = "minecraft:iron_ingot", count = 30},
}
local totaux = {}
for _, pile in ipairs(cases) do
  totaux[pile.name] = (totaux[pile.name] or 0) + pile.count
end
for nom, nombre in pairs(totaux) do
  print(nom, nombre)
end
Écran
minecraft:iron_ingot    94
minecraft:coal  12

Les dernières mesures. Gardez les 5 valeurs les plus récentes d'un capteur : ajoutez à la fin, retirez la première quand il y en a trop. La recette Enregistreur de données et graphique utilise la même idée.

Brass
local historique = {}
local function retenir(valeur)
  table.insert(historique, valeur)
  if #historique > 5 then
    table.remove(historique, 1)
  end
end
for vitesse = 10, 80, 10 do
  retenir(vitesse)
end
print(table.concat(historique, " "))
Écran
40 50 60 70 80

Une grille. Une table de rangées, chaque rangée une table de cases : grille[y][x]. Créez chaque rangée avant de vous en servir :

Brass
local grille = {}
for y = 1, 3 do
  grille[y] = {}
  for x = 1, 4 do
    grille[y][x] = "."
  end
end
grille[2][3] = "#"
for y = 1, 3 do
  print(table.concat(grille[y]))
end
Écran
....
..#.
....