Hi Alex,
Sit tight, grab some coffee, and brace yourself, for this is about to be a quite lengthy post.
It might be a bit off-topic, but will indeed teach you one of things, and hopefully you will find the rest on your own.
Please, bear with me, as english is not my primary language; in case anything is unclear, let me know, so that I can provide more details.
I will discuss tables in general, then some subleties/gotchas about the way Lua handles tables. Then, I will come back on how you can maintain a list of
highscores and perform some various common operations on this list of scores. Then I will discuss briefly about serialization.
Let us proceed.
Tables
In programming, in general, the term "table" always refer to the same thing : "a data structure". Or simply said, a nice way to organize information. Tables are
very, very, very handy, just because they let you group, put together pieces of information or data that belong together, or just keep them together and easily process them like items on a list.
If I am holding a store, and I want people to easily find what they are looking for, I will show them a list of things I sell in my store. Better, I will sort that list of items alphabetically. This list is indeed "a table". If I am writing a game where there would be dozens of ennemies attacking the player, I will list those baddies in a table. The advantage of having such a data structure is I can keep all those entities tight together at the same place, and then I can easily loop through the collection and apply a specific action on some specific entities in a very straightforward and elegant manner.
In Lua, there are two types of tables. You have "
arrays" and "
associative arrays", also called "
hash tables".
Arrays are tables where indices start at 1, and are only numeric.
Only numeric.
And consecutive. That is the whole point.
The following is a table, more precisely an array, containing three elements. The first element is "a" (index is 1), second is "b" (index is 2), third is "c" (index is 3).
A very common operation one can do on "arrays" is
iteration: looping through the entire collection. But most importantly, in order. The traversal order of an array is
predictable, because you know for sure that the first index will be 1, and that indices are consecutive integers.So assuming
t is an array, you can proceed as follows to print all the contents of the array.
Code: Select all
for index = 1, #t do
local value = t[index]
print(index, value)
end
Here, I have used then length operator "#" which returns the size of an array (i.e, the number of elements it contains).
This way of doing iteration is called "
numeric for loop".
An equivalent method would be using Lua's dedicated function,
ipairs.
Code: Select all
for index, value in ipairs(t) do
print(index, value)
end
Of course, arrays can have
holes, but generally you do not want such thing to happen.
Also, you can have indices which are less than 1. But that won't make it behave like an array anymore.
Code: Select all
t = {1,2,3,4,5} -- an array of 5 elements
t[4] = nil -- creates a hole
t[0] = 0
t[-1] = -1
This is still a valid table, but not a convenient array. Because
ipairs, for example, will stop yielding values at the hole, and will not start iterating at index -1, nor at index 0, as the first expected index is assumed to be 1.
Of course, there are ways to handle such tables to retrieve all elements (using
pairs, or
next), but for simple use cases, you won't have to deal with such arrays.
"
Associative arrays" are a bit more complex than arrays, but they are actually the reason for what Lua's is beautiful, and quite powerful (IMHO). "
Associative arrays" are basically tables, in the sense that they hold a set of elements (values) together, but those elements are not stored in any particular order. Instead, indices are not (consecutive) integers, starting from 1, but they can be any Lua first-class value : a decimal number, an integer, a string, a function, a table, a coroutine, anything.
In most of use cases, people will use strings to index values in "associative arrays". For instance, If I want to represent a player entity in my game, I need to keep track of his position on the screen, a pair of
x,y coordinates. I also want to track down his
health, his
money, etc. I would use then an "associative array":
Code: Select all
player = { x = 10, y = 20, health = 50, money = 0}
Here, the table player contains 4 values. They are indexed with strings : "x", "y", "health" and "money". I can retrieve any of these values using the "dot notation".
Code: Select all
print(player.x) --> 10
print(player.y) --> 20
print(player.health) --> 50
print(player.money) --> 0 (poor dude!)
Note that here, there is no particular order for all those 4 values: noone can say if
money comes before
health, or
x coordinate should come before
y. In "associative array", there is no particular order, as I was saying before. Therefore, I cannot use the
numeric for loop, nor
ipairs, because all those assume that there is an order defined. Therefore, for associative arrays, I will use something more general. Conveniently, Lua's provide a function for that, named
pairs.
Code: Select all
for key, value in pairs(player) do
print(key, value)
end
This will print out the contents of the "associative array" player, but not in a predictable order. But it will definitely process
all the elements inside that "associative array".
Side note,
pairs also does what
ipairs does, as it is much more general. So yes, using
pairs on a simple array will work, too. But
ipairs cannot do what
pairs does, so using
ipairs on an "associative array" will produce nothing.
Generally, Lua users tend to use
pairs when they are uncertain of the contents of a table. If one is not sure a table is an array, one will just use
pairs to be sure to get all the elements inside. And in case I am definitely sure a table is an array, I will just use
ipairs, or a
numeric for loop.
But arrays and associative arrays can be mixed. Yes.
Code: Select all
t = {x = "x", y = "y", 1, z = "z", 2, 3}.
What Lua does here is creating an
associative array that will have two parts: a
hash part, and an
array part.
Using
ipairs or
numeric for will only process the array part. you will get pairs (1,1), (2,2) and (3,3).
Using
pairs will process the whole contents of the table, in no particular order though.
And i'll stop here for tables. I will leave some links for complementary details I left out, and I will strongly recommend to read them later. My point is, Lua's primary data structure are tables. So once you understand them, and how powerful they are, you can do lots of things on your own with the language. That won't be time wasted.
Now we want highscores.
You want to maintain a list of
highscores. I will outline here a simple method to handle it. I won't strive here for efficiency, but for simplicity and effectiveness.
Let us start with an empty array.
I would like to add a new score. Since the array is empty, I will just have to insert it.
Code: Select all
table.insert(highscores, newscore)
Quite simple. But let' say I also want to keep track of a maximum number of scores
n.
In other words, I don't want the array to contain more than
n elements. Then, if I have a new score I have to check first if this new score is worth being added: if yes, I will insert it and remove the lowest score. If not, the new score will just be dropped.
Let us write a routine (a function, more precisely) that will handle this for us gracefully.
Let us assume the
table is already sorted in decreasing order : the higher scores come first, the lowest ones come last.
Code: Select all
-- highscores is the array of scores, newscore is the score to be added and n is the max number of scores
-- to keep track of.
function addscore(highscores, newscore, n)
-- get the lowest score, at the end of the list
local lowestScore = highscores[n]
if newscore > lowestScore then
table.remove(highscores, n) -- the size of the table becomes now n-1
table.insert(highscores, newscore) -- adds at the end of the actual table the new score.
table.sort(highscores, function(a,b) return a>b end) -- sort the table in decreasing order
end
end
The above function can be implemented in a different way. No need to remove and add again a new entry in the array; you can just assign the new value to the last index. Simpler.
Code: Select all
function addscore(highscores, newscore, n)
-- get the lowest score, at the end of the list
local lowestScore = highscores[n]
if newscore > lowestScore then
highscores[n] = newscore
table.sort(highscores, function(a,b) return a>b end) -- sort the table in decreasing order
end
end
Also, you can handle the table in a different manner : instead of keeping scores in decreasing order, you can maintain it in
increasing order. The lowest scores come first, and the higher ones come last. In that way, to add a new score, you will have to compare it to the first entry (at index 1). If it is higher, then it is worth being added.
Code: Select all
function addscore(highscores, newscore, n)
-- get the lowest score, at the end of the list
local lowestScore = highscores[1]
if newscore > lowestScore then
highscores[1] = newscore
table.sort(highscores) -- sort the table in increasing order
-- table.sort(highscores, function (a,b) return a<b end) would do exactly the same than just "table.sort(highscore)".
end
end
In case you want to print the list of scores, since the table is an array, just use
ipairs or a
numeric for to loop through values and print them.
Note that, in case
highscores are listed in increasing order, you will have to loop from the last index to the first one to get the higher values first. Like this:
Code: Select all
highscores = {100,200,300,400}
for i = #highscores, 1, -1 do -- numeric for, iterating backwards
print(highscores[i]) -- will print 400, 300, 200, then 100.
end
This is quite easier to get as soon as you are familiar with the basic operations on tables. You might even come up with something nicer on your own, and modify it in case you want to keep track of best scores associated with names: you just need to handle an array of associative arrays. This is getting fancier.
Serializing ?
Last, thing, serialization. Well, the point here is, you have a table in memory. And you want to write it down in a file so that you can read it and restore it back in memory again as it was. Since that array (and its contents) are data, you will have to turn it it into a format that you can write down (readable or not) and in a way such you can get the exact same information without any losses. This is called
serialization.
Yes indeed, there are lots of serialization libraries around, you can get them and use them (see the
wiki), but you can also try to write some serialization function for simple use cases: arrays, associative array with string keys, etc. See
How can I dump out a table ? (Luafaq). Libraries are relevant in the case the table you want to serialize contains fancy things, such a cyclic references, metatables associated to the table, keys that are functions, userdata, or tables themselves...
Hope all of this will help.