Learn Lua in Neovim: Table Array Operations — Sort, Concat & Custom Comparators | Episode 18
Video: Learn Lua in Neovim: Table Array Operations — Sort, Concat & Custom Comparators | Episode 18 by Taught by Celeste AI - AI Coding Coach
Lua Table Array Operations: sort, concat, and Custom Comparators
table.sort(t)for ascending sort.table.sort(t, fn)with a custom comparator for any order.table.concat(t, sep)joins to a string. Build a to-do list.
Last episode covered creating and modifying arrays. Today we cover the three high-leverage operations on them: sorting, joining, and inserting at specific positions.
Sort: ascending by default
local names = { "Charlie", "Alice", "Eve", "Bob", "Diana" }
table.sort(names)
for i = 1, #names do
print(names[i])
end
Output:
Alice
Bob
Charlie
Diana
Eve
table.sort(t) sorts the table in place (the original is modified). Default order is ascending by <. For strings, that's lexicographic. For numbers, numeric.
Sorts in place. If you need the original order preserved, copy first:
local copy = {}
for i, v in ipairs(names) do copy[i] = v end
table.sort(copy)
Concat: join to a string
local names = { "Alice", "Bob", "Charlie" }
print("Joined: " .. table.concat(names, ", "))
Output:
Joined: Alice, Bob, Charlie
table.concat(t, sep) builds one string by joining the array elements with sep between them. The separator is optional:
print(table.concat(names)) -- "AliceBobCharlie"
print(table.concat(names, " | ")) -- "Alice | Bob | Charlie"
table.concat only works on string and number values. Booleans and tables error.
Custom sort comparator
local scores = { 85, 92, 78, 95, 88 }
table.sort(scores)
print("Ascending: " .. table.concat(scores, ", "))
-- Ascending: 78, 85, 88, 92, 95
table.sort(scores, function(a, b)
return a > b
end)
print("Descending: " .. table.concat(scores, ", "))
-- Descending: 95, 92, 88, 85, 78
Pass a function as the second argument: table.sort(t, comparator). The comparator takes two elements a and b and returns:
trueifashould come beforeb.falseotherwise.
Default comparator is function(a, b) return a < b end. To reverse, return a > b.
Sorting tables of objects
local people = {
{ name = "Charlie", age = 30 },
{ name = "Alice", age = 25 },
{ name = "Bob", age = 35 },
}
-- Sort by name (alphabetical)
table.sort(people, function(p, q) return p.name < q.name end)
-- Sort by age (youngest first)
table.sort(people, function(p, q) return p.age < q.age end)
-- Sort by age descending
table.sort(people, function(p, q) return p.age > q.age end)
The comparator can reach into any field. This generalises to any "sort by X" need.
Stable vs unstable sort
table.sort is not a stable sort — equal elements may swap order. For most cases this is invisible. When stability matters:
-- Add an "original index" to break ties stably
for i, p in ipairs(people) do p.orig_idx = i end
table.sort(people, function(p, q)
if p.age ~= q.age then return p.age < q.age end
return p.orig_idx < q.orig_idx
end)
Verbose but reliable.
A to-do list
local todos = { "Buy groceries", "Clean kitchen" }
table.insert(todos, "Walk the dog") -- append at end
table.insert(todos, 1, "Wake up early") -- insert at position 1
print("To-Do:")
for i = 1, #todos do
print(" " .. i .. ". " .. todos[i])
end
-- Output:
-- 1. Wake up early
-- 2. Buy groceries
-- 3. Clean kitchen
-- 4. Walk the dog
table.remove(todos, 2) -- complete and remove item 2
print("After completing #2:")
for i = 1, #todos do
print(" " .. i .. ". " .. todos[i])
end
-- Output:
-- 1. Wake up early
-- 2. Clean kitchen
-- 3. Walk the dog
table.insert(todos, 1, "...") shifts everything down. Useful for "add at top" semantics; expensive for big lists (O(n) shifts).
For "first in, first out" queue behaviour, append at the end with plain table.insert(t, v) and remove from the front with table.remove(t, 1). For LIFO (stack), append and table.remove(t) at the end.
table.unpack: spread a table into args
local args = {1, 2, 3}
print(table.unpack(args)) -- 1 2 3
The opposite of {...}. Useful when you have a table and want to pass its contents to a function:
local args = {2, 8, 5, 1, 9}
print(math.max(table.unpack(args))) -- 9
In Lua 5.1, this was just unpack (global). Lua 5.2+ moved it to table.unpack. Use the namespaced version in modern code.
Common stumbles
Sorting in place when you wanted to preserve original. table.sort(t) mutates t. Copy first if you need both.
Comparator must return strict less-than. Returning a <= b violates the strict-ordering requirement and can cause undefined behaviour. Always use < or >.
table.concat on non-string values. {1, true, "hi"} errors because of true. Convert first: table.concat({tostring(true), "hi"}, " ").
Off-by-one in inserts. table.insert(t, 0, x) doesn't add to "before the first" — Lua clamps. To prepend, use position 1.
Mutating during iteration. Sorting a list while iterating it produces undefined results. Mutate first, iterate after.
What's next
Episode 19: tables as dictionaries. Same {} syntax, but with string keys: { name = "Alice", age = 30 }. Dot notation vs bracket notation. Adding, modifying, and deleting keys.
Recap
table.sort(t) for in-place ascending sort. table.sort(t, function(a, b) return a > b end) for custom order. Comparator must be strict less-than. table.concat(t, sep) joins to a string (string/number values only). table.insert(t, pos, v) for positional inserts; table.remove(t, pos) to shift things out. table.unpack(t) spreads a table back into multiple args.
Next episode: tables as dictionaries.