Tables as arrays
Using a table as a list, growing and trimming it with insert and remove, walking it with ipairs, and sorting it in place.
One container, many uses
Lua has a single container called a table. Used as a list it holds values in order, numbered from one. local colors = {"red", "green", "blue"} makes a table of three strings.
You reach a value by its number: the first string is at index one. The first index is one, not zero, and building that habit early saves a hundred off-by-one mistakes.
The length sign, and the ends
The # in front of a table gives the number of items in its list part, so the length of that colors table is three. table.insert adds to the end, and table.remove takes from the end or from a position you name.
Removing an item closes the gap: the items after it move down one, so the list stays numbered from one to its length with no holes left behind.
Walking a list
The generic for with ipairs walks a list in order, handing you the index and the value on each pass. It stops at the first missing index, which is exactly the end of the list.
table.sort sorts the items in place, using their natural order. Give it a second function that compares two values and you can sort by any rule you like.
What the length sign measures
A table is an associative array: every value sits under a key. A list is the special case where the keys are 1, 2, 3 and so on with no gaps. #t returns a border — an index n where t[n] is present and t[n + 1] is not — and while the list is dense that is simply its length.
Leave a hole and the answer can be any border the table happens to have. ipairs has the same weak spot: it walks from one and stops at the first nil, so items after a hole are invisible. Keep lists dense and both behave.
Insert, remove, and the order of a sort
table.insert(t, v) puts v at #t + 1; with an index it shifts the later items up. table.remove(t, i) does the reverse and returns the value it took out. Both keep the list dense, which is why you reach for them instead of assigning slots by hand.
table.sort rearranges the table in place and returns nothing. With no comparison it uses the natural order of numbers or strings. With one, that function must answer true only when its first argument belongs before the second; a rule that says yes in both directions leaves the sort with no consistent order.
Building a list one item at a time
The idiom t[#t + 1] = value appends to a dense list: #t is the last index, adding one names the next free slot. table.insert(t, value) does the same thing and says what you mean, so prefer it in new code. Both rely on the list having no holes; with a gap, #t may name a border in the middle.
Appending is cheap because the table grows its storage in steps rather than on every write. The cost is amortised, the same doubling idea a dynamic array uses. What is not cheap is inserting at the front: every later item shifts up one, so a queue built that way costs more with every arrival.
Removing while you walk
Removing an item during an ipairs walk is the classic way to skip one. The loop reads index i, removes it, and the next item slides into i; the loop then moves to i + 1 and never sees it. The list is correct afterwards, but the visit was not.
Walk backwards when you must remove: for i = #t, 1, -1 do visits the last item first, so a removal only shifts items the loop has already passed. The other safe shape is to build a new list of the items to keep and leave the original alone. Both are easier to check than a forward loop with a manual index adjustment.
A list that changes
local colors = {"red", "green", "blue"}
print(#colors)
print(colors[1])
table.insert(colors, "gold")
print(colors[#colors])
table.remove(colors, 2)
for i, color in ipairs(colors) do
print(i .. ": " .. color)
endThree list mistakes. Treating index zero as the first item, which gives nil and then an error on the next operation. Removing items with table.remove while an ipairs loop walks the same list, which skips the item that slides into the gap. And expecting table.sort to give back a sorted copy: it changes the table you passed and returns nothing.
Try these next
- Build a list of five words, sort it, and print it with table.concat.
- Insert a word at position two, remove the last one, and print the length after each step.
- Sort three numbers with a comparison that puts the larger one first.
A basket of fruit
Run it and watch the list change after each edit. Then add a fruit of your own to the end, remove the first one, and sort the numbers in a different order.
A queue, and a leaderboard
Run it and follow the queue as it shrinks. Then add a fourth score and print the top three with their places.
A table used as a list should stay dense, filled from one with no gaps. The length sign and ipairs are reliable only while it is, so remove an item with table.remove rather than leaving its slot empty.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is the index of the first item in a Lua list?
