Tables
Tables are Lua’s only data structure. They serve as arrays, dictionaries, objects, and modules.
Table Basics
Creating Tables
-- Empty table
local t = {}
-- Array-style (sequential integer keys starting at 1)
local hosts = {"ise-01", "ise-02", "ise-03"}
-- Dictionary-style (string keys)
local config = {
hostname = "ise-01",
port = 443,
verify_ssl = true
}
-- Mixed (avoid when possible)
local mixed = {
"first", -- [1] = "first"
"second", -- [2] = "second"
name = "mixed", -- ["name"] = "mixed"
}
-- Explicit keys
local explicit = {
[1] = "one",
[2] = "two",
["key"] = "value",
[{}] = "table as key" -- Any value can be a key
}
Accessing Elements
local hosts = {"ise-01", "ise-02", "ise-03"}
local config = {hostname = "ise-01", port = 443}
-- Array access (1-indexed!)
print(hosts[1]) -- "ise-01"
print(hosts[2]) -- "ise-02"
-- Dictionary access
print(config["hostname"]) -- "ise-01"
print(config.hostname) -- "ise-01" (syntactic sugar)
-- Non-existent keys return nil
print(hosts[10]) -- nil
print(config.missing) -- nil
Modifying Tables
local hosts = {"ise-01", "ise-02"}
local config = {hostname = "ise-01"}
-- Add/update element
hosts[3] = "ise-03"
config.port = 443
config["verify_ssl"] = true
-- Remove element
hosts[2] = nil -- Creates hole in array!
config.port = nil -- Removes key
-- Array manipulation (use table functions)
table.insert(hosts, "ise-04") -- Append
table.insert(hosts, 1, "ise-00") -- Insert at position 1
table.remove(hosts, 2) -- Remove at position 2
table.remove(hosts) -- Remove last
-- Get length (array part only)
print(#hosts) -- Counts sequential integer keys from 1
Arrays
Working with Arrays
local endpoints = {}
-- Build array
for i = 1, 5 do
endpoints[i] = "endpoint-" .. i
end
-- Append
table.insert(endpoints, "endpoint-6")
endpoints[#endpoints + 1] = "endpoint-7" -- Same effect
-- Iterate (ipairs for arrays)
for i, ep in ipairs(endpoints) do
print(i, ep)
end
-- Length
print(#endpoints)
-- Check if empty
if #endpoints == 0 then
print("No endpoints")
end
-- Last element
print(endpoints[#endpoints])
Array Operations
-- Sort
local hosts = {"ise-03", "ise-01", "ise-02"}
table.sort(hosts)
-- hosts = {"ise-01", "ise-02", "ise-03"}
-- Sort with custom comparator
table.sort(hosts, function(a, b)
return a > b -- Descending
end)
-- Reverse (no built-in, manual)
local function reverse(t)
local n = #t
for i = 1, n // 2 do
t[i], t[n - i + 1] = t[n - i + 1], t[i]
end
end
-- Concatenate to string
local str = table.concat(hosts, ", ") -- "ise-01, ise-02, ise-03"
local str = table.concat(hosts, "\n") -- Newline separated
-- Slice (no built-in, manual)
local function slice(t, start, stop)
local result = {}
for i = start, stop or #t do
result[#result + 1] = t[i]
end
return result
end
Common Array Patterns
-- Filter
local function filter(t, predicate)
local result = {}
for _, v in ipairs(t) do
if predicate(v) then
result[#result + 1] = v
end
end
return result
end
local active = filter(hosts, function(h)
return string.match(h, "^ise")
end)
-- Map
local function map(t, fn)
local result = {}
for i, v in ipairs(t) do
result[i] = fn(v)
end
return result
end
local upper_hosts = map(hosts, string.upper)
-- Reduce
local function reduce(t, fn, initial)
local acc = initial
for _, v in ipairs(t) do
acc = fn(acc, v)
end
return acc
end
local total = reduce({1, 2, 3, 4}, function(acc, v)
return acc + v
end, 0) -- 10
-- Find
local function find(t, predicate)
for i, v in ipairs(t) do
if predicate(v) then
return v, i
end
end
return nil
end
local ise_host = find(hosts, function(h)
return string.match(h, "ise")
end)
-- Contains
local function contains(t, value)
for _, v in ipairs(t) do
if v == value then return true end
end
return false
end
Dictionaries
Working with Dictionaries
local config = {
hostname = "ise-01",
port = 443,
verify_ssl = true
}
-- Add/update
config.timeout = 30
config["max_retries"] = 3
-- Iterate (pairs for dictionaries)
for key, value in pairs(config) do
print(key .. " = " .. tostring(value))
end
-- Check key exists
if config.timeout then
print("Timeout is set")
end
if config.missing == nil then
print("Key doesn't exist")
end
-- Get with default
local timeout = config.timeout or 30
-- Keys and values
local function keys(t)
local result = {}
for k in pairs(t) do
result[#result + 1] = k
end
return result
end
local function values(t)
local result = {}
for _, v in pairs(t) do
result[#result + 1] = v
end
return result
end
Dictionary Operations
-- Merge (shallow)
local function merge(t1, t2)
local result = {}
for k, v in pairs(t1) do result[k] = v end
for k, v in pairs(t2) do result[k] = v end
return result
end
local defaults = {timeout = 30, retries = 3}
local user_config = {timeout = 60}
local config = merge(defaults, user_config)
-- {timeout = 60, retries = 3}
-- Deep merge
local function deep_merge(t1, t2)
local result = {}
for k, v in pairs(t1) do
if type(v) == "table" then
result[k] = deep_merge(v, {})
else
result[k] = v
end
end
for k, v in pairs(t2) do
if type(v) == "table" and type(result[k]) == "table" then
result[k] = deep_merge(result[k], v)
else
result[k] = v
end
end
return result
end
-- Pick specific keys
local function pick(t, keys)
local result = {}
for _, k in ipairs(keys) do
result[k] = t[k]
end
return result
end
local subset = pick(config, {"hostname", "port"})
Nested Tables
Creating Nested Structures
-- ISE deployment structure
local deployment = {
nodes = {
{
hostname = "ise-01",
ip = "10.50.1.20",
roles = {"PAN", "MNT"},
status = "running"
},
{
hostname = "ise-02",
ip = "10.50.1.21",
roles = {"PSN"},
status = "running"
}
},
version = "3.2",
patches = {"patch1", "patch2", "patch3"}
}
-- Access nested
print(deployment.nodes[1].hostname) -- "ise-01"
print(deployment.nodes[1].roles[1]) -- "PAN"
-- Safe nested access
local function get_nested(t, ...)
local current = t
for _, key in ipairs({...}) do
if type(current) ~= "table" then
return nil
end
current = current[key]
end
return current
end
local ip = get_nested(deployment, "nodes", 1, "ip") -- "10.50.1.20"
local missing = get_nested(deployment, "nodes", 99, "ip") -- nil
Modifying Nested Tables
-- Add node
table.insert(deployment.nodes, {
hostname = "ise-03",
ip = "10.50.1.22",
roles = {"PSN"},
status = "pending"
})
-- Update nested value
deployment.nodes[1].status = "maintenance"
-- Safe set nested
local function set_nested(t, value, ...)
local keys = {...}
local current = t
for i = 1, #keys - 1 do
local key = keys[i]
if current[key] == nil then
current[key] = {}
end
current = current[key]
end
current[keys[#keys]] = value
end
set_nested(deployment, "patched", "nodes", 1, "patch_status")
Table as Object
-- Simple object pattern
local Endpoint = {}
function Endpoint.new(mac, ip, vlan)
return {
mac = mac,
ip = ip,
vlan = vlan
}
end
function Endpoint.is_active(ep)
return ep.status == "active"
end
local ep = Endpoint.new("00:11:22:33:44:55", "10.50.10.100", 10)
ep.status = "active"
print(Endpoint.is_active(ep)) -- true
-- Method syntax (with metatables, covered later)
-- ep:is_active()
Reference vs Copy
-- Tables are passed by reference
local original = {a = 1, b = 2}
local reference = original
reference.a = 99
print(original.a) -- 99 (modified!)
-- Shallow copy
local function shallow_copy(t)
local copy = {}
for k, v in pairs(t) do
copy[k] = v
end
return copy
end
local copy = shallow_copy(original)
copy.a = 1
print(original.a) -- 99 (unchanged)
-- Deep copy
local function deep_copy(t)
if type(t) ~= "table" then return t end
local copy = {}
for k, v in pairs(t) do
copy[k] = deep_copy(v)
end
return copy
end
local nested = {config = {port = 443}}
local deep = deep_copy(nested)
deep.config.port = 9060
print(nested.config.port) -- 443 (unchanged)
Neovim Table Utilities
-- vim.tbl_* functions
-- Deep extend (merge)
local result = vim.tbl_deep_extend("force",
{a = 1, nested = {x = 1}},
{b = 2, nested = {y = 2}}
)
-- {a = 1, b = 2, nested = {x = 1, y = 2}}
-- Check if table is list (array)
vim.tbl_islist({1, 2, 3}) -- true
vim.tbl_islist({a = 1}) -- false
-- Check if empty
vim.tbl_isempty({}) -- true
-- Count entries
vim.tbl_count({a = 1, b = 2}) -- 2
-- Filter
local filtered = vim.tbl_filter(function(v)
return v > 2
end, {1, 2, 3, 4}) -- {3, 4}
-- Map
local mapped = vim.tbl_map(function(v)
return v * 2
end, {1, 2, 3}) -- {2, 4, 6}
-- Keys
local keys = vim.tbl_keys({a = 1, b = 2}) -- {"a", "b"}
-- Values
local values = vim.tbl_values({a = 1, b = 2}) -- {1, 2}
-- Contains value
vim.tbl_contains({1, 2, 3}, 2) -- true
Next Module
Functions - Definitions, closures, variadic functions.