Bringing More to the Table with Lua (Part 1)

This is the fifth entry in my Lua Tutorial series. See here for an introduction for this series, or here for our previous entry covering loops and similar.

Tables are an integral data structure in Lua. They operate like both standard arrays and like hash tables in other languages. They have a huge number of uses and some really cool features, but for the time being, we’re just going to introduce them as a basic data structure and see a bit of what they can do. Down the line when we get to Object Oriented Programming and similar, we will revisit tables. This lesson is going to focus on some more formal terms for what we’ve been learning and to set up the stage for further lessons.

The most basic use of a table is as an array. In our variables tutorial, we learned about basic variables, specifically scalars. We didn’t really dive into terminology then, but a scalar is basically a variable that holds one thing while an array holds multiple things in series. If you are coming from something like C or similar, Lua arrays will look familiar, but have some awesome features most languages don’t have, and some features which are not better nor worse, but different as well.

The first thing to note about Lua arrays is that they are not a fixed size. A lot of languages require you to instantiate an array with a fixed size. This means that the array cannot be expanded past this size in a traditional sense (there usually exists some mechanism to expand arrays, but it can be extremely inefficient). The next important thing to note is that Lua arrays customarily start with an index of 1 rather than 0. If you have never used any other programming languages before, this doesn’t really matter, but if you are familiar with basically any other commonly used language, this is an important bit of information.

Setting up a table as an array is very simple. Let’s start with:

animals = { "cat", "dog", "bird", "rat", "fish" }

This sets up an array of “animals” which has 5 elements. This is cool, but how do we access any of these elements? This is really easy, we just use:

array[ [index] ]

This will give us the value at the specified index. If we request a value which is not indexed, Lua will return nil, which is a special value that symbolizes a complete lack of value and is distinct from 0 or “”. Let’s try this out!

animals = { "cat", "dog", "bird", "rat", "fish" }

print( animals[ 1 ] )
print( animals[ 2 ] )
print( animals[ 3 ] )
print( animals[ 4 ] )
print( animals[ 5 ] )

print( animals[ 6 ] )

This results in:

cat
dog
bird
rat
fish
nil

Obviously, this isn’t that useful, but we can combine this with a for loop to make this much cleaner:

animals = { "cat", "dog", "bird", "rat", "fish" }

for i=1, 5, 1 do
	print( animals[ i ] )
end

Now that we can work with an array, we need to be able to change values or else this isn’t really worth much. Changing values is done by just referencing the individual piece as a scalar like follows:

animals = { "cat", "dog", "bird", "rat", "fish" }

for i=1, 5, 1 do
	print( animals[ i ] )
end

print( "" )
animals[ 3 ] = "bat"

for j=1, 5, 1 do
	print( animals[ j ] )
end

You should see the following when run:

cat
dog
bird
rat
fish

cat
dog
bat
rat
fish

With minimal work, we could expand this and use our for loop to query for data and put this at whatever index we wanted, but this can get pretty messy. Let’s learn a new trick for working with tables. The table library which is built in provides some cool features for this. We can use:

table.insert( [table], [value] ) - Insert the value value at the end of table and resize accordingly
table.insert( [table], [position], [value] ) - Insert the value value at the position in the table and resize and reorder as necessary
table.remove( [table] ) - Remove the last element and resize the table
table.remove( [table], [position] ) - Remove the element at position and resize the table

To see this in action, we can run the following:

array = {}

for i=1, 5, 1 do
        io.write( "What do you want to put in the array? " )
        name = io.read()
        table.insert( array, name )
end

for j=1, 5, 1 do
        print( array[ j ] )
end

Which will look something like:

What do you want to put in the array? A
What do you want to put in the array? B
What do you want to put in the array? C
What do you want to put in the array? D
What do you want to put in the array? E
A
B
C
D
E

When we run: array = {} we are initializing an empty array to have something to insert into. We can further clean this up by learning a new function, ipairs:

array = {}

for i=1, 5, 1 do
        io.write( "What do you want to put in the array? " )
        name = io.read()
        table.insert( array, name )
end

for j, value in ipairs( array ) do
        print( value )
end

ipairs is a simple function which takes a table and returns an interator and the value at said position in the array. This means you don’t really need to worry about the size of the array, you can run through the entire thing easily without calculating any of this information out. There are other ways to get the size of an array itself which can be fed into the original for loop such as the # operator and table.getn( [table] ) (though both of these do have some specific caveats):

array = {}

for i=1, 5, 1 do
        io.write( "What do you want to put in the array? " )
        name = io.read()
        table.insert( array, name )
end

for j, value in ipairs( array ) do
        print( value )
end

print( "size: " .. #array )
print( "size: " .. table.getn( array ) )

There are other functions for tables as applied to arrays and a lot more than just this which we will cover down the line. The next lesson will cover the use of tables as hash tables which will help us spice the functionality of what we can do up. From there, we will proceed onto more loops and logic using some of the new things we have learned and focus on building some more useful tasks.