# [TUTORIAL] Introduction to Lua tables (2nd edition)

## Recommended Posts

**MAJOR UPDATE TO THIS TUTORIAL**

Hi,

This tutorial has been going through a major update and expansion since the first release. Special thanks deserved to @petergebruers and others for helping me understand many of the underlying table concepts and for also contributing a lot to this tutorial. It may be a good idea to paste some of the code in this tutorial into a test scene to understand how they work by reviewing the output

Topics covered with lots of sample code and expected outputs

• Table basics
• Creating tables and printing  (ordered,  non-ordered, numerical and non numerical keys)
• #operator - pros and cons
• Nested Tables - printing, updating, inserting,
• Summary of key points
• Storing tables in global variables

I would have found this very useful when I started so hopefully some users will find it useful as well.

-frank & peter

Tables

A table is a tree structure, consisting of field and each field has a "key" and "value" pair.

The key can be any Lua value except nil and NaN. A table can store anything except nil.

The name of the table is the variable that stores a reference to the table so it can be accessed

Creating Tables and displaying the table data

a = {}

This is the smallest table and stores the reference to the table {} in the variable a

Consider the following table

```print("table with numerical keys")
fav = { = "4FM",  = "96FM",  = "Calm",  = "Heart",  = "Red_FM"}```

fav is the table (or at least the name of the variable where the table is stored),

in the above table 'fav', it has 5 fields and each field consists of a key/value pair

4 is the key and 4FM is the value , 2 is a key and 96FM is the value, etc

You can display all the key/value pairs using the following ...

```print("table with numerical keys and using k/v pairs to display the fields but they won't be in order")
fav = { = "4FM",  = "96FM",  = "Calm",  = "Heart",  = "Red_FM"}

for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end```

the result is ... and you'll notice that the results are NOT returned in order

if you have numerical keys ,  then you will need to use ipairs to return them in order

```print("table with numerical keys and using k/v ipairs to display the fields in order")

fav = { = "4FM",  = "96FM",  = "Calm",  = "Heart",  = "Red_FM"}

for k,v in ipairs(fav) do print("k: " ..k..", v: "..v) end```

#operator

The #operator (#tablename) is used by a lot of people to identify the length of the table and use this to iterate through the table but it is not always recommended

an example of where it will work is here

```print("table with numerical keys and using the #operator to iterate through the table")
radio = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

if the table keys are numerical and contiguous, it will work..

....but if they are not or you have inserted or removed values then it won't work

A good example of where it doesn't work very well is here

```print("table where numerical keys are not explicitly defined but are still the table keys")
print(" example where the #operator doesnt return the correct count")
radio = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

for k,v in pairs(radio) do print("k: " ..k..", v: "..v) end

-- remove one value, set it to nil

for k,v in pairs(radio) do print("k: " ..k..", v: "..v) end

you can see that the second loop in the debug is showing an #operator value of 5 (because it is the last key but there are only 4 key/value pairs as we removed one

Another approach that is recommended to use is as follows

use the k/v approach to count the fields and then use the counter to iterate through..

```print("table where numerical keys are not explicitly defined but are still the table keys")
print(" example using k/v pairs to calculate the correct table field count")

radio = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

counter = 0
for k,v in pairs(radio) do counter=counter+1 end
print("counter: "..counter)
for i = 1, counter do print(radio[i]) end

radio = nil -- remove the row by setting the key to nil

counter = 0
for k,v in pairs(radio) do counter=counter+1 end
print("counter: "..counter)
for i = 1, counter do print(radio[i]) end```

and you can see that the counter is correct in both loops

Updating Tables

you can update a table by setting Key/Value pairs  or delete a value as follows

```print("table where numerical keys are not explicitly defined but are still the table keys")
print("examples of updating the table")

fav = {"4FM", "96FM", "Calm", "Heart", "Red_FM"}

-- update one value add another value
fav = "Soul"
fav = "Classic"

-- delete value at key 1
fav = nil

for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end```

if the k/v pair already exists it overwrites it or of the k/v pair didn't exist, it simply adds it as above

This is the same example but the table has the numerical keys explicitly defined

```print("updating a table")
fav = {
 = "4FM",
 = "96FM",
 = "Calm",
}

fav = "Soul"
fav = "Classic"

for k,v in ipairs(fav) do print("k: " ..k..", v: "..v) end```

Example of adding key/value pairs to this table using table.insert

```fav= {"4FM","96FM", "Soul", "Jazz"}
table.insert(fav, "Calm")
table.insert(fav, "Red_FM")
table.insert(fav, "Heart")
for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end```

The extra key/value pairs will be added as extra fields as you cannot specify the exact field you want to update as in the approach above

Tables with non-numerical keys are also possible as shown below

```print("Table initialiser with alpha keys")
fav = {
["a"] = "4FM",
["b"] = "96FM",
["c"] = "Calm",
["d"] = "Heart",
["e"] = "Red_FM"}
for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end```

the same table can be represented in a simpler form

```print("Table initialiser with alpha keys - simplified")
fav = {
a = "4FM",
b= "96FM",
c= "Calm",
d = "Heart",
e= "Red_FM"}
for k,v in pairs(fav) do print("k: " ..k..", v: "..v) end```

If you chose or have to use non numerical keys or a mix, then Lua makes no guarantee on order as the spec says random.

The only way to order a table with non-numerical keys (such as alphabetical order) is to use an external reference.

Nested Tables

Lets take the following table, it looks like each value in the key/value pair is a table in itself as if one table is nested inside the other

```table = {
 = {loc="utility_room", name="HeatingTemp"},
 = {loc="bed_room", name="FreezerTemp"},
}

for k,v in pairs(table) do print(k,v) end```

if you run the usual k/v code you will see the second/nested set of tables

To list the k/v inside the second table you need to iterate through the first table

we use a counter like before to find out how many fields are in the outer most table

There are two tables nested within the outer most table

• table  which has two fields with k/v pairs
• table  which has two fields with k/v pairs
```print("Nested table example")
table = {
 = {loc="utility_room", name="HeatingTemp"},
 = {loc="bed_room", name="FreezerTemp"},
}
for k,v in pairs(table) do
print("first level key: " ..k..", first level value: "..tostring(v))
for k2,v2 in pairs(v) do -- This works because we KNOW v is a table.
print(" second level key: " ..k2..", second level value: "..v2)
end
end```

we then use the counter to expose the k/v pairs at table and table  with the following result

You could also expose specific values (in the nested able) using the following

```table = {
 = {loc="utility_room", name="HeatingTemp"},
 = {loc="bed_room", name="FreezerTemp"},
}

for k,v in pairs(table) do

print("location: "..table[k].loc)

end```

with the following expected result You could also have used the #operator that we discussed earlier BUT only because the key's in the outer-most table are numerical and contingous

```for i = 1, #table do  -- #list refers to the max number of items in the table

fibaro:debug(table[i].ip, table[i].port)

end```

Updating a value in a nested table

If you want to update a specific value in the nested table you need to use the k/v method we have discussed

```fav = {
 = {loc="utility_room", name="HeatingTemp"},
 = {loc="bed_room", name="FreezerTemp"},
}

-- update nested table on the second field with a new value
fav.loc = "kitchen"
fav.name = "Hobtemp"

-- or you could use the following format to do accomplish the same update
fav["loc"] = "kitchen"
fav["name"] = "Hobtemp"

-- print out the updated table...

for k,v in pairs(fav) do

for k,v in pairs(fav[k]) do print("k: " ..k..", v: "..v) end

end

-- second option to print out the updated table with more explanation
for k,v in pairs(fav) do
print("first level key: " ..k..", first level value: "..tostring(v))
for k2,v2 in pairs(v) do -- This works because we KNOW v is a table.
print(" second level key: " ..k2..", second level value: "..v2)
end
end```

the result is

If you want to add/insert a new k/v pair in the nested level, you can use the table.insert method

```fav = {
 = {loc="utility_room", name="HeatingTemp"},
 = {loc="bed_room", name="FreezerTemp"},
}

-- insert a new value
table.insert(fav,{loc="bedroom", name="sinktemp"})

-- print out the updated table...
for k,v in pairs(fav) do

for k,v in pairs(fav[k]) do print("k: " ..k..", v: "..v) end

end

-- second option to print out the updated table with more explanation
for k,v in pairs(fav) do
print("first level key: " ..k..", first level value: "..tostring(v))
for k2,v2 in pairs(v) do -- This works because we KNOW v is a table.
print(" second level key: " ..k2..", second level value: "..v2)
end
end```

and the result is three fields in the table So before we go to the last task of storing the table in a global variable, I thought it would be good to have a quick recap..

Summary

A table is a tree structure, consisting of field and each field has a "key" and "value" pair.

The name of the table is the variable that stores a reference to the table so it can be accessed
you can explicitly declare a numerical key in a table but you don't have to

• for a table with a numerical key - use ipairs to print the table fields in order
• #operator can be used but ONLY for tables with numerical keys that are contiguous
• it is recommended to use the standard k/v approach to count the number of fields
• remove a value by setting its key = nil
• update a table using the table[key] method
• add a new field using table.insert(table, "value") and will add a numerical key
• table.insert will not work with tables with non-numerical keys
• tables with non-numerical keys cannot be printed in order by ipairs, result will be random
• nested tables require iteration to print the inner-most tables
• multiple levels of iteration required for multi-nested tables
• updating nested table - iterate with k/v and use table[key].nested-key = "value"
• inserting into nested table - iterate with k/v and use table.insert(table,{key="value, key="value}
• tables can be very confusing and frustrating to us newbie's , so take your time Storing a table in a global variable

if you take the following table structure

```Table = {
group = {
option1=1560,option2=1507,option3=881
},
}```

If you want to store this table in a global variable you will need to encode the table as a string. One of the most common formats to encode a table (structure) is json

from json.org

"JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate."

To save the table you need to encode it  and then save it to the global variable

```jTable = json.encode(table)
fibaro:setGlobal("StateTable", jTable)```

In order to read back a single value you need to decode the string

`local jT = json.decode(fibaro:getGlobalValue("Table"))`

once it's decoded you can then read one of the values or manipulate the table

`fibaro:debug(jT.group.option1)`

Updating a single value

- assign the new value, encode it and the store it.....

```jT.group.option3 = 987
jTable = json.encode(jT)
fibaro:setGlobal("StateTable", jTable)```

Making json strings/output easier to read...

Sometimes a json string can be difficult to read and understand its structure. Typically they would look something like this...

`{"widget": {"debug": "on","window": {"title": "Sample Konfabulator Widget","name": "main_window","width": 500,"height": 500},"image": {"src": "Images/Sun.png","name": "sun1","hOffset": 250,        "vOffset": 250,"alignment": "center"},"text": {"data": "Click Here","size": 36,"style": "bold","name": "text1","hOffset": 250,"vOffset": 100,"alignment": "center","onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"}}}`

There are a number of websites that will 'pretify' the string make it easier to read the structure and enable you to understand how you might navigate it.

If you take json string above and paste it into http://jsonprettyprint.com/ it will display it more like a table

This format is easier to understand

```{
"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images\/Sun.png",
"name": "sun1",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity \/ 100) * 90;"
}
}
}```

Finally....

There is a very good discussion on using a table in a predefined variables to store the ID's of scenes and devices for the HC2

suggestions to improve welcome

-f

Edited by AutoFrank
• 5
• 2
##### Share on other sites

Some advanced work with tables can be found here

Edited by AutoFrank
##### Share on other sites

Thanks @AutoFrank !!

##### Share on other sites

Nice work, wanted to look it up on the iteration. But this saves me work ##### Share on other sites

I put this in one of my scenes:

lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}

--print("#operator: "..#lamp_ON)
for i = 1, #lamp_ON do
if  tonumber(fibaro:getValue(lamp_ON, "value")) > 0 then
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..lamp_ON);
end
end

Output in debug if light is ON:

Forgotten to Turn this Lamp OFF: 79 < Lamp ID

But instead of the Lamp ID I would like to have a text.. like:

Forgotten to Turn this Lamp OFF: Kitchen Table

How to accomplish this?

##### Share on other sites

@samuel, the following should work:

fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:get(lamp_ON, 'name'));

Best, Rene.

##### Share on other sites

i get this error:

[DEBUG] 14:41:48: line 115: attempt to concatenate local 'deviceId' (a table value)

-- table with Lamp ID's
lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}

--print("#operator: "..#lamp_ON)
for i = 1, #lamp_ON do
if  tonumber(fibaro:getValue(lamp_ON, "value")) == 0 then -- > 0
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..lamp_ON);
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:get(lamp_ON, 'name'));
end
end

##### Share on other sites
9 minutes ago, samuel said:

i get this error:

[DEBUG] 14:41:48: line 115: attempt to concatenate local 'deviceId' (a table value)

-- table with Lamp ID's
lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}

--print("#operator: "..#lamp_ON)
for i = 1, #lamp_ON do
if  tonumber(fibaro:getValue(lamp_ON, "value")) == 0 then -- > 0
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..lamp_ON);
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:get(lamp_ON, 'name'));
end
end

Hi @samuel

As far as I can see you may need to use a call to the HC2 api using the device id to determine the device name

I don't have time this minute to do a full solution but here is the api if it helps

```local http = net.HTTPClient()
http:request("http://127.0.0.1:11111/api/devices/"..id.."", {
options = { method = 'GET', headers = {}, timeout = 2000 },
success = function(status)
fibaro:debug("s-s: "..status.status)
if status.status ~= 200 and status.status ~= 201 then print("failed"); end
response = status.data
print("res: "..response)
result  = json.decode(response)

end,
error = function(err)
print('[ERROR] ' .. err)
end
})```

you'll need to pass the 'id' to the http:request string

'result' is the http response table that has the name you need.

##### Share on other sites
2 hours ago, AutoFrank said:

Hi @samuel

As far as I can see you may need to use a call to the HC2 api using the device id to determine the device name

(...)

The API is certainly a generic solution, but HC has a builtin function to determine the name of a device. Like this (plus some other corrections to the code):

```lamp_ON = {79, 148, 153, 238, 239, 284, 298, 299, 312, 313, 314, 314, 322}
for k,v in pairs(lamp_ON) do
if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > 0
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
fibaro:debug("Forgotten to Turn this Lamp OFF: "..(fibaro:getName(v) or ""));
end
end```

Edit: fix type of table values and add nil test.

Edited by petergebruers
##### Share on other sites
5 minutes ago, petergebruers said:

The API is certainly a generic solution, but HC has a builtin function to determine the name of a device. Like this (plus some other corrections to the code):

```lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do
if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > Not 0 means "on"
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));
end
end```

thanks @petergebruers - more like what @ReneNL was referring to .... ##### Share on other sites

39 minutes ago, petergebruers said:

The API is certainly a generic solution, but HC has a builtin function to determine the name of a device. Like this (plus some other corrections to the code):

```lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do
if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > Not 0 means "on"
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));
end
end```

i got an error: [DEBUG] 15:48:54: line 36: Assertion failed: Expected number

Line 36 below:

`fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));`

in pairs,

I tried the one from @ Autofrank with ..id.. ..(79).. for a try, then i got the whole json string as output...

Edited by samuel
##### Share on other sites

the table input are the id's from the lights

`lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}`
##### Share on other sites

**MAJOR UPDATE TO THIS TUTORIAL**

Hi,

This tutorial has been going through a major overhaul and expansion since the first release. Special thanks deserved to @petergebruers and others for helping me understand many of the underlying table concepts and for also contributing a lot to this tutorial. It may be a good idea to paste some of the code in this tutorial into a test scene to understand how they work by reviewing the output

Topics covered with lots of sample code and expected outputs

• Table basics
• Creating tables and printing  (ordered,  non-ordered, numerical and non numerical keys)
• #operator - pros and cons
• Nested Tables - printing, updating, inserting,
• Summary of key points
• Storing tables in global variables

I would have found this very useful when I started so hopefully some users will find it useful as well.

-frank & peter

Edited by AutoFrank
##### Share on other sites
56 minutes ago, samuel said:

i got an error: [DEBUG] 15:48:54: line 36: Assertion failed: Expected number

Line 36 below:

`fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(v));`

in pairs,

I tried the one from @ Autofrank with ..id.. ..(79).. for a try, then i got the whole json string as output...

slight tweak - turning v into a number

Try this

```lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do
if  tonumber(fibaro:getValue(v, "value")) == 0 then -- > Not 0 means "on"
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));
end
end```

##### Share on other sites
1 hour ago, AutoFrank said:

slight tweak - turning v into a number

Try this

```lamp_ON = {"79", "148", "153", "238", "239", "284", "298", "299", "312", "313", "314", "314", "322"}
for k,v in pairs(lamp_ON) do
if  tonumber(fibaro:getValue(v, "value")) == 0 then -- > Not 0 means "on"
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));
end
end```

Thanks. Yes... I didn't catch that because I tested the code on my HC2, and purely, out of habit, I defined the lua table like this:

`lamp_ON={4,9}`

You see the important difference? No quotes! So my values were all of type "number", while the original question defined them as strings! It's syntactic sugar, again!

I also fixed another issue, in case you use a wrong ID and your device doesn't exist:

```lamp_ON = {79, 148, 153, 238, 239, 284, 298, 299, 312, 313, 314, 314, 322}
for k,v in pairs(lamp_ON) do
if  tonumber(fibaro:getValue(v, "value")) ~= 0 then -- > 0
--fibaro:debug("Forgotten to Turn this Lamp OFF: " ..v);
fibaro:debug("Forgotten to Turn this Lamp OFF: "..(fibaro:getName(v) or ""));
end
end```

This is very idiomatic Lua:

(fibaro:getName(v) or "")

and it means: get the name, or alternatively use an empty string if the name was nil. This is needed because the concatenation operator refuses to add nil to a string and aborts the script...

##### Share on other sites

Thanks guys: did did the trick:

`fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));`
##### Share on other sites
6 minutes ago, samuel said:

Thanks guys: did did the trick:

`fibaro:debug("Forgotten to Turn this Lamp OFF: " ..fibaro:getName(tonumber(v)));`

Great news

if you use the rest of @petergebruers code it'll give you a bit more resilience

##### Share on other sites

so i did thanks

##### Share on other sites

How to compare a value from a table, so that an action can be performed?

table = {
 = "dim",
 = "bright",
 = "off",
}

if value from number  == "dim" then

-- code

end

##### Share on other sites
6 hours ago, samuel said:

How to compare a value from a table, so that an action can be performed?

(...)

Here's an educational example. Does this help?

```local table = {
 = "dim",
 = "bright",
 = "off",
}
local tmp
for k,v in ipairs(table) do
if v  == "dim" then
print ("Entry nr "..k.." in this table has value '"..v.."'")
tmp=k -- if you want to use v or k outside the loop, assign it to an "outer variable"
break -- exit the loop, if you only want the first match
end
end
print("Variable tmp has value: "..(tmp or "nil")) -- "nil" means: the value is not in the table```

## Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account. ×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
• FIBARO.COM