Tables

Array tables

They are like the LSL lists, but starting with index 1 instead of 0.

Tables use { and } instead of [ and ]:

  • local myTab1 = {} -- empty table
    • like list myTab1 = [];
  • local myTab = { "apples", "bananas", "oranges" }
    • like list myTab = [ "apples", "bananas", "oranges" ];

The operator # returns the length of an array table:

  • local length = #myTab
    • like integer length = llGetListLength(myTab2);

This also works, but is longer and slower:

  • local length = ll.GetListLength(myTab)

To get a value in the table using [ and ] with the index number:

  • local value = myTab[2] -- > bananas
    • like string value = ll.List2String(myTab,1) -- > bananas

The functions ll.List2*** also work, but it’s better to access the table in the SLua way.

To modify a value is also using [ and ] with the index number:

  • myTab[3] = "kiwis"

There is no operator + for tables. To add an element to the end of a table we can get the length of the table and add the next one:

  • myTab[ #myTab + 1 ] = "melons"
    • like myTab = myTab + "melons";

Or with a function in the library table:

  • table.insert( tabFruits, "Pear" )

The function table.insert(), unlike ll.ListInsertList(), doesn’t return a value. It modifies the table that we have sent as parameter. We don’t assign the return value to the table.

To insert at the start we use the same function but with 3 parameters. Now the second parameter is the index before which the element will be inserted.:

  • table.insert( tabFruits, 1, "Lemon" )

Or anywhere in the table, the index in the second parameter must be between 1 and #table:

  • table.insert( tabFruits, 3, "Lemon" )

If we use an index out of the range, the value will be inserted with this index, but all the index values in between will not exist. It will not be an array table and the functions for array tables and the operator # will not work.

To remove the last element in an array table:

  • table.remove( tabFruits )

table.remove(), unlike ll.DeleteSubList(), modifies the table and doesn’t return the table. It returns the value of the removed key.

To remove any element in the table we have the same function with a second paramenter, the index of the element:

  • table.remove( tabFruits, 3 )

To add one table to another:

  • table.move( tabExoticFruits, 1, #tabExoticFruits, #tabFruits + 1, tabFruits )
    • like tabFruits = tabFruits + tabExoticFruits

In this example table.move() copies the first table (tabExoticFruits) from the index 1 to the index #tabExoticFruits (from start to end), into the second table (tabFruits) starting at #tabFruits + 1 (at the end).

table.move() copies or moves a range of elements from one part of an array table to another part or into a different array table. It returns the modified table. It’s more efficient that copying with a loop and It handles overlapping ranges correctly.

The parameters of table.move() are:

  • table to be copied
  • copy from index (1, from start)
  • copy to index (#table, to the end)
  • index where to insert the copied table (#table + 1, to add after; 1, to add before)
  • table to be copied to (optional, if omitted it copies to the same table)

Another example with table.move:

  • local newTab = table.move(myTab, 4, 6, 1, {})
    • like list newList = llList2List( myTab, 3, 5 )

Use of table.move() to copy two tables into another one:

  • local myTab3 = table.move(myTab2, 1, #myTab2, #myTab1 + 1, table.move(myTab1, 1, #myTab1, 1, {}))
    • like list myList3 = myList1 + myList2

To make a string with the elements of the table:

  • local myStr = table.concat( tabFruits, ", " )
    • like string myStr = llDumpList2String( tabFruits, ", " )

But table.concat() only works with the types number and string. Any other type (boolean or any SLua type) throws an error.

To get the largest positive numerical key in the table (array or dictionary):

  • local maxNumKey = table.maxn()

To create a pre-filled array table, optionally filling it with a default value:

  • local myTotals = table.create( 10, 0 ) -- array from 1 to 10, initialized at 0

It’s used for performance and memory optimization when creating large arrays and to initialize to some value.

To assign the elements in a table to several variables:

  • local name, descr, pos, rot = unpack(ll.GetObjectDetails(id, { OBJECT_NAME, OBJECT_DESC, OBJECT_POS, OBJECT_ROT }))

unpack() converts the elements in an array to a series of single values that are assigned to the variables in the same order.

Tables in SLua are much more efficient than lists in LSL. Tables are a core feature of Lua and they are used everywhere for many things. Tables are very optimized by the compiler.
It’s better to stop using the LL lists functions and use the tables in Lua style.

Dictionary tables

They are pairs of key-value, like the linkset data.

To create a dictionary table:

  • local tabFruitsQuantity = { Apple = 50, Banana = 30, Cherry = 20, Orange = 15 }

Instead of a list of values, we use keys (the name of the fruit) and values (the quantity of each fruit).

To add a new pair of key value:

  • tabFruitsQuantity["Melon"] = 5

Or, only when the key would be a valid identifier:

  • tabFruitsQuantity.Melon = 5

Using one format or the other is a matter of preference, internally both are the same.

To modify a value is also assigning a value to it, replacing the previous value.

To get a value from the table:

  • ll.OwnerSay( tabFruitsQuantity["Melon"] ) -- > 5

Or:

  • ll.OwnerSay( tabFruitsQuantity.Melon ) -- > 5

If the key doesn’t exist it returns a value of nil:

  • ll.OwnerSay( tabFruitsQuantity["Pumpkin"] ) -- > nil

To remove a pair of key value:

  • tabFruitsQuantity["Cherry"] = nil

The key is removed (it’s not set to nil) and the memory is cleaned up.

Array tables are a special case of dictionary tables, where the keys are consecutive numbers starting with 1.

All values, in array tables and in dictionary tables, can be anything, including another table. Tables in tables can also have other tables as values, to make lists of lists of lists…

The operator # only works with array tables, not with dictionary tables.

To know if a dictionary table is empty:

  • next( myTab ) == nil

Copying tables

In this example:

local tab1 = { 10, 20, 30 }
local tab2 = {}

tab2 = tab1
tab1[2] = 15  -- changing a value in tab1

ll.OwnerSay(tostring(tab1[2]))  -- > 15  -- ok
ll.OwnerSay(tostring(tab2[2]))  -- > 15  -- tab2 is also changed!

Tables can be a big thing. They are not stored in the variables. Variables store a reference to the table.

tab2 = tab1 copies the reference to the table, not the table itself. Both variables have the same reference to the only one table.

We can make a copy of a table, with the table.clone function:

  • tab2 = table.clone( tab1 )

Now there are two different tables (with the same values), each one with its reference.

It makes a “shallow” copy, only the elements in the first level of the table are copied. If an element is a table, this “sub-table” is not copied, and the new table has the same reference to the “sub-table”.

It’s useful in functions that receive tables as parameters, when we want to modify the table in the function, but not the original table outside the function. For instance, when translating LSL code, where the functions always receive a copy of the lists passed as parameters:

  • paramTab = table.clone(paramTab)

table.clone() can be used to add two tables into another one:

  • local myTab3 = table.move(myTab2, 1, #myTab2, #myTab1 + 1, table.clone(table1))

Comparing tables

In SLua myTab1 == myTab2 doesn’t compare the elements of the tables, it compares the references of the tables. If myTab1 and myTab2 have a reference to the same table the comparison is true, otherwise is false.

-- Comparing tables (SLua)

local table1 = { 10, 20, 30 }
local table2 = { 10, 20, 30 }
print( table1 == table2 )  -- >  false

local table3 = { 10, 20, 30 }
local table4 = table3
print( table3 == table4 )  -- >  true

In LSL myList1 == myList2 doesn’t compare the elements of the lists neither, it compares the length of the lists.

The LSL myList1 == myList2 in SLua is:

  • #myTab1 == #myTab2

And the LSL myTab == [] to check if the list is empty is:

  • #myTab == 0

Comparing with not equal is a bit more tricky, in LSL if ( list1 != list2 ) { returns the difference of length:

  • to get a boolean result:
    • if #table1 ~= #table2 then
  • to get a number with the difference:
    • local diff = #table1 - #table2

And the LSL alternative to llGetListLength(), integer len = list1 != [];, is just:

  • local len = #table1

Sorting tables

As a example let’s use a table of farm animals and their products:

local farmAnimals = {
	Cow = "Milk",
	Chicken = "Eggs",
	Sheep = "Wool",
	Pig = "Meat",
	Goat = "Milk",
	Duck = "Eggs",
	Horse = "Labor",
}
  
for animal, product in pairs(farmAnimals) do
	ll.OwnerSay(animal .. " -> " .. product)
end

--[[
result:

Chicken -> Eggs
Pig -> Meat
Duck -> Eggs
Sheep -> Wool
Goat -> Milk
Horse -> Labor
Cow -> Milk
]]

Dictionary tables don’t have a defined order, their keys can appear in any order when looping on them.

Dictionary tables can’t be sorted, only array tables can. The solution is to make an array table with the keys in the dictionary, the names of the animals, and to sort this array table.

The array table with the animals:

local sortedAnimals = {}

for animal in pairs(farmAnimals) do
	table.insert(sortedAnimals, animal)
end

And sorting the table:

table.sort(sortedAnimals)

for _, animal in ipairs(sortedAnimals) do
	ll.OwnerSay(animal .. " -> " .. farmAnimals[animal])
end

--[[
result:

Chicken -> Eggs
Cow -> Milk
Duck -> Eggs
Goat -> Milk
Horse -> Labor
Pig -> Meat
Sheep -> Wool
]]

The result is the table sortedAnimals with the keys of the table farmAnimals sorted in alphabetical ascending order. This is the default order of table.sort(). But it can sort in any order, for instance, by product and then by animal.

table.sort() has a second parameter, a function, that will be called and passed two parameters (let’s call them “a” and “b”) which are two elements in the array list.
The function has to return a boolean value, true if a goes before b, false if b before goes a. Since this function is only used in table.sort(), it can be defined as an inline, unnamed function:

table.sort(sortedAnimals, function(a, b)
  if farmAnimals[a] == farmAnimals[b] then  -- comparing products
  	return a < b  -- if products are equal, sorting by name
  else
  	return farmAnimals[a] < farmAnimals[b]  -- sorting by product
  end
end)

for _, animal in ipairs(sortedAnimals) do
	ll.OwnerSay(farmAnimals[animal] .. " <- " .. animal)
end

--[[
result:

Eggs <- Chicken
Eggs <- Duck
Labor <- Horse
Meat <- Pig
Milk <- Cow
Milk <- Goat
Wool <- Sheep
]]

Sparse array tables

The operator # and the library functions for array tables work perfectly if the table is an array with consecutive indexes starting with 1. But if there are missing values (nils) in the array their behaviour is sometimes surprising.

As example, this table x, a sparse array missing the indexes 3, 5, 7, 11, 14 and 15.

x = {}
for _, v in { 1, 2, 4, 6, 8, 9, 10, 12, 13, 16 } do
	x[v] = 1
end

Let’s try different things with the table x.

print(#x)  -- > 16
print(ll.GetListLength(x))  -- > 16

It looks well with #. The LL function returns the same.

for index, value in ipairs(x) do
	print(index, value)
end
-- > 1 1
-- > 2 1

It only prints two pairs of key-values. ipairs() stops at the first nil at index 3.

x[4] = "hello"
print(table.find(x, "hello"))  -- > nil

It doesn’t find the hello. table.find(), like ipairs(), stops at the first nil at index 3.

table.insert(x, "bye")
print(x[17])  -- > bye

table.insert() always inserts at #t+1.

print(#x)  -- > 13
print(ll.GetListLength(x))  -- > 13

After inserting the element 17, now the length is 13.
We can only trust that #t always exist and #t+1 never exists (always nil)

table.remove(x, 17)
print(x[17])  -- > bye

Removing the element 17, but the element is still there. table.remove() only removes from 1 to #t and ignores calls with other indexes.
If the element is from 1 to #t, it is deleted and the elements after it until #t are moved one index down. But the elements after #t are not moved.