Lua - Arrays

Overview

Estimated time: 25–30 minutes

Arrays in Lua are implemented using tables with consecutive integer keys starting from 1. This tutorial covers array creation, manipulation, common operations, and algorithms for working with array-like data structures effectively.

Learning Objectives

  • Master array creation and initialization techniques
  • Learn array manipulation and modification methods
  • Understand array iteration patterns and best practices
  • Explore multidimensional arrays and nested structures
  • Apply common array algorithms and operations

Prerequisites

  • Understanding of tables and basic table operations
  • Knowledge of loops and iteration
  • Familiarity with functions

Array Creation and Initialization

Arrays in Lua are tables with consecutive integer indices starting from 1:

-- Empty array
local empty_array = {}
print("Empty array length:", #empty_array)

-- Array with initial values
local fruits = {"apple", "banana", "orange", "grape"}
print("Fruits array:")
for i = 1, #fruits do
    print(i .. ":", fruits[i])
end

-- Array with mixed types (not recommended for pure arrays)
local mixed = {1, "hello", true, 3.14}
print("\nMixed array:")
for i = 1, #mixed do
    print(i .. ":", mixed[i], type(mixed[i]))
end

-- Array with explicit indices
local explicit = {
    [1] = "first",
    [2] = "second",
    [3] = "third"
}
print("\nExplicit indices:")
for i = 1, #explicit do
    print(i .. ":", explicit[i])
end

-- Creating arrays with table.insert
local numbers = {}
for i = 1, 5 do
    table.insert(numbers, i * 2)  -- Insert at end
end
print("\nNumbers array:", table.concat(numbers, ", "))

Expected Output:

Empty array length:	0
Fruits array:
1:	apple
2:	banana
3:	orange
4:	grape

Mixed array:
1:	1	number
2:	hello	string
3:	true	boolean
4:	3.14	number

Explicit indices:
1:	first
2:	second
3:	third

Numbers array:	2, 4, 6, 8, 10

Array Access and Modification

Accessing and modifying array elements:

local colors = {"red", "green", "blue"}

-- Access elements (1-indexed)
print("Array access:")
print("First color:", colors[1])
print("Second color:", colors[2])
print("Last color:", colors[#colors])

-- Modify elements
colors[2] = "yellow"  -- Change green to yellow
print("After modification:", table.concat(colors, ", "))

-- Add elements at specific positions
colors[4] = "purple"  -- Add at end
print("After adding purple:", table.concat(colors, ", "))

-- Insert element at specific position
table.insert(colors, 2, "orange")  -- Insert orange at position 2
print("After inserting orange:", table.concat(colors, ", "))

-- Remove elements
local removed = table.remove(colors, 3)  -- Remove element at position 3
print("Removed:", removed)
print("After removal:", table.concat(colors, ", "))

-- Remove last element
local last = table.remove(colors)  -- Remove from end
print("Removed last:", last)
print("Final array:", table.concat(colors, ", "))

-- Out of bounds access
print("Element at index 10:", colors[10])  -- Returns nil
print("Array length after operations:", #colors)

Expected Output:

Array access:
First color:	red
Second color:	green
Last color:	blue
After modification:	red, yellow, blue
After adding purple:	red, yellow, blue, purple
After inserting orange:	red, orange, yellow, blue, purple
Removed:	yellow
After removal:	red, orange, blue, purple
Removed last:	purple
Final array:	red, orange, blue
Array length after operations:	3

Array Iteration Patterns

Different ways to iterate through arrays:

local numbers = {10, 20, 30, 40, 50}

-- Method 1: Numeric for loop
print("Method 1 - Numeric for loop:")
for i = 1, #numbers do
    print("Index " .. i .. ":", numbers[i])
end

-- Method 2: ipairs (recommended for arrays)
print("\nMethod 2 - ipairs:")
for index, value in ipairs(numbers) do
    print("Index " .. index .. ":", value)
end

-- Method 3: Generic for with pairs (not recommended for arrays)
print("\nMethod 3 - pairs:")
for key, value in pairs(numbers) do
    print("Key " .. key .. ":", value)
end

-- Reverse iteration
print("\nReverse iteration:")
for i = #numbers, 1, -1 do
    print("Index " .. i .. ":", numbers[i])
end

-- Iteration with conditions
print("\nValues greater than 25:")
for i, value in ipairs(numbers) do
    if value > 25 then
        print("Index " .. i .. ":", value)
    end
end

-- Finding elements
local function find_value(arr, target)
    for i, value in ipairs(arr) do
        if value == target then
            return i
        end
    end
    return nil
end

local search_value = 30
local found_index = find_value(numbers, search_value)
print("\nFound " .. search_value .. " at index:", found_index or "not found")

Expected Output:

Method 1 - Numeric for loop:
Index 1:	10
Index 2:	20
Index 3:	30
Index 4:	40
Index 5:	50

Method 2 - ipairs:
Index 1:	10
Index 2:	20
Index 3:	30
Index 4:	40
Index 5:	50

Method 3 - pairs:
Key 1:	10
Key 2:	20
Key 3:	30
Key 4:	40
Key 5:	50

Reverse iteration:
Index 5:	50
Index 4:	40
Index 3:	30
Index 2:	20
Index 1:	10

Values greater than 25:
Index 3:	30
Index 4:	40
Index 5:	50

Found 30 at index:	3

Array Operations and Algorithms

Common array operations and algorithms:

Basic Array Operations

-- Array utility functions
local array_utils = {}

-- Copy array (shallow copy)
function array_utils.copy(arr)
    local copy = {}
    for i, value in ipairs(arr) do
        copy[i] = value
    end
    return copy
end

-- Reverse array in-place
function array_utils.reverse(arr)
    local n = #arr
    for i = 1, math.floor(n / 2) do
        arr[i], arr[n - i + 1] = arr[n - i + 1], arr[i]
    end
    return arr
end

-- Find maximum value and index
function array_utils.max(arr)
    if #arr == 0 then return nil, nil end
    local max_val, max_idx = arr[1], 1
    for i = 2, #arr do
        if arr[i] > max_val then
            max_val, max_idx = arr[i], i
        end
    end
    return max_val, max_idx
end

-- Find minimum value and index
function array_utils.min(arr)
    if #arr == 0 then return nil, nil end
    local min_val, min_idx = arr[1], 1
    for i = 2, #arr do
        if arr[i] < min_val then
            min_val, min_idx = arr[i], i
        end
    end
    return min_val, min_idx
end

-- Sum all elements
function array_utils.sum(arr)
    local total = 0
    for _, value in ipairs(arr) do
        total = total + value
    end
    return total
end

-- Test the utility functions
local test_array = {3, 1, 4, 1, 5, 9, 2, 6}
print("Original array:", table.concat(test_array, ", "))

local copied = array_utils.copy(test_array)
print("Copied array:", table.concat(copied, ", "))

local max_val, max_idx = array_utils.max(test_array)
print("Maximum value:", max_val, "at index", max_idx)

local min_val, min_idx = array_utils.min(test_array)
print("Minimum value:", min_val, "at index", min_idx)

print("Sum of elements:", array_utils.sum(test_array))

array_utils.reverse(test_array)
print("Reversed array:", table.concat(test_array, ", "))

Expected Output:

Original array:	3, 1, 4, 1, 5, 9, 2, 6
Copied array:	3, 1, 4, 1, 5, 9, 2, 6
Maximum value:	9	at index	6
Minimum value:	1	at index	2
Sum of elements:	31
Reversed array:	6, 2, 9, 5, 1, 4, 1, 3

Searching and Filtering

-- Search and filter operations
local search_utils = {}

-- Linear search
function search_utils.linear_search(arr, target)
    for i, value in ipairs(arr) do
        if value == target then
            return i
        end
    end
    return nil
end

-- Binary search (requires sorted array)
function search_utils.binary_search(arr, target)
    local left, right = 1, #arr
    
    while left <= right do
        local mid = math.floor((left + right) / 2)
        if arr[mid] == target then
            return mid
        elseif arr[mid] < target then
            left = mid + 1
        else
            right = mid - 1
        end
    end
    
    return nil
end

-- Filter array elements
function search_utils.filter(arr, predicate)
    local result = {}
    for _, value in ipairs(arr) do
        if predicate(value) then
            table.insert(result, value)
        end
    end
    return result
end

-- Find all indices where condition is true
function search_utils.find_all(arr, predicate)
    local indices = {}
    for i, value in ipairs(arr) do
        if predicate(value) then
            table.insert(indices, i)
        end
    end
    return indices
end

-- Count elements matching condition
function search_utils.count(arr, predicate)
    local count = 0
    for _, value in ipairs(arr) do
        if predicate(value) then
            count = count + 1
        end
    end
    return count
end

-- Test search and filter operations
local test_numbers = {1, 5, 3, 8, 2, 7, 4, 6}
local sorted_numbers = {1, 2, 3, 4, 5, 6, 7, 8}

print("Test array:", table.concat(test_numbers, ", "))

-- Linear search
local target = 7
local found_index = search_utils.linear_search(test_numbers, target)
print("Linear search for " .. target .. ":", found_index)

-- Binary search on sorted array
print("Sorted array:", table.concat(sorted_numbers, ", "))
local binary_index = search_utils.binary_search(sorted_numbers, target)
print("Binary search for " .. target .. ":", binary_index)

-- Filter even numbers
local even_numbers = search_utils.filter(test_numbers, function(x) return x % 2 == 0 end)
print("Even numbers:", table.concat(even_numbers, ", "))

-- Find all indices of numbers > 5
local large_indices = search_utils.find_all(test_numbers, function(x) return x > 5 end)
print("Indices of numbers > 5:", table.concat(large_indices, ", "))

-- Count odd numbers
local odd_count = search_utils.count(test_numbers, function(x) return x % 2 == 1 end)
print("Count of odd numbers:", odd_count)

Expected Output:

Test array:	1, 5, 3, 8, 2, 7, 4, 6
Linear search for 7:	6
Sorted array:	1, 2, 3, 4, 5, 6, 7, 8
Binary search for 7:	7
Even numbers:	8, 2, 4, 6
Indices of numbers > 5:	2, 4, 6, 8
Count of odd numbers:	4

Array Transformation

Transform arrays using mapping and reduction operations:

-- Transformation operations
local transform_utils = {}

-- Map function to each element
function transform_utils.map(arr, func)
    local result = {}
    for i, value in ipairs(arr) do
        result[i] = func(value)
    end
    return result
end

-- Reduce array to single value
function transform_utils.reduce(arr, func, initial)
    local accumulator = initial
    for _, value in ipairs(arr) do
        accumulator = func(accumulator, value)
    end
    return accumulator
end

-- Create array with range of values
function transform_utils.range(start, stop, step)
    step = step or 1
    local result = {}
    for i = start, stop, step do
        table.insert(result, i)
    end
    return result
end

-- Flatten nested arrays (one level)
function transform_utils.flatten(arr)
    local result = {}
    for _, value in ipairs(arr) do
        if type(value) == "table" then
            for _, nested_value in ipairs(value) do
                table.insert(result, nested_value)
            end
        else
            table.insert(result, value)
        end
    end
    return result
end

-- Chunk array into smaller arrays
function transform_utils.chunk(arr, size)
    local result = {}
    for i = 1, #arr, size do
        local chunk = {}
        for j = i, math.min(i + size - 1, #arr) do
            table.insert(chunk, arr[j])
        end
        table.insert(result, chunk)
    end
    return result
end

-- Test transformation operations
local numbers = {1, 2, 3, 4, 5}
print("Original numbers:", table.concat(numbers, ", "))

-- Map: square each number
local squares = transform_utils.map(numbers, function(x) return x * x end)
print("Squared:", table.concat(squares, ", "))

-- Map: convert to strings
local strings = transform_utils.map(numbers, tostring)
print("As strings:", table.concat(strings, ", "))

-- Reduce: sum all numbers
local sum = transform_utils.reduce(numbers, function(acc, x) return acc + x end, 0)
print("Sum using reduce:", sum)

-- Reduce: find maximum
local max = transform_utils.reduce(numbers, function(acc, x) return math.max(acc, x) end, numbers[1])
print("Max using reduce:", max)

-- Range generation
local range1 = transform_utils.range(1, 10)
local range2 = transform_utils.range(0, 20, 3)
print("Range 1-10:", table.concat(range1, ", "))
print("Range 0-20 step 3:", table.concat(range2, ", "))

-- Flatten nested arrays
local nested = {{1, 2}, {3, 4}, {5, 6}}
local flattened = transform_utils.flatten(nested)
print("Flattened:", table.concat(flattened, ", "))

-- Chunk array
local data = {1, 2, 3, 4, 5, 6, 7, 8, 9}
local chunks = transform_utils.chunk(data, 3)
print("Chunked into groups of 3:")
for i, chunk in ipairs(chunks) do
    print("Chunk " .. i .. ":", table.concat(chunk, ", "))
end

Expected Output:

Original numbers:	1, 2, 3, 4, 5
Squared:	1, 4, 9, 16, 25
As strings:	1, 2, 3, 4, 5
Sum using reduce:	15
Max using reduce:	5
Range 1-10:	1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Range 0-20 step 3:	0, 3, 6, 9, 12, 15, 18
Flattened:	1, 2, 3, 4, 5, 6
Chunked into groups of 3:
Chunk 1:	1, 2, 3
Chunk 2:	4, 5, 6
Chunk 3:	7, 8, 9

Multidimensional Arrays

Working with matrices and multidimensional data:

-- 2D array (matrix) operations
local matrix_utils = {}

-- Create 2D array filled with value
function matrix_utils.create(rows, cols, value)
    value = value or 0
    local matrix = {}
    for i = 1, rows do
        matrix[i] = {}
        for j = 1, cols do
            matrix[i][j] = value
        end
    end
    return matrix
end

-- Print 2D array
function matrix_utils.print(matrix)
    for i = 1, #matrix do
        local row = ""
        for j = 1, #matrix[i] do
            row = row .. string.format("%4d ", matrix[i][j])
        end
        print(row)
    end
end

-- Get matrix dimensions
function matrix_utils.dimensions(matrix)
    local rows = #matrix
    local cols = rows > 0 and #matrix[1] or 0
    return rows, cols
end

-- Matrix addition
function matrix_utils.add(matrix1, matrix2)
    local rows1, cols1 = matrix_utils.dimensions(matrix1)
    local rows2, cols2 = matrix_utils.dimensions(matrix2)
    
    if rows1 ~= rows2 or cols1 ~= cols2 then
        error("Matrix dimensions must match for addition")
    end
    
    local result = matrix_utils.create(rows1, cols1)
    for i = 1, rows1 do
        for j = 1, cols1 do
            result[i][j] = matrix1[i][j] + matrix2[i][j]
        end
    end
    return result
end

-- Transpose matrix
function matrix_utils.transpose(matrix)
    local rows, cols = matrix_utils.dimensions(matrix)
    local result = matrix_utils.create(cols, rows)
    for i = 1, rows do
        for j = 1, cols do
            result[j][i] = matrix[i][j]
        end
    end
    return result
end

-- Example usage
print("2D Array (Matrix) operations:")

-- Create and fill matrices
local matrix1 = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
}

local matrix2 = {
    {9, 8, 7},
    {6, 5, 4},
    {3, 2, 1}
}

print("Matrix 1:")
matrix_utils.print(matrix1)

print("\nMatrix 2:")
matrix_utils.print(matrix2)

-- Matrix addition
local sum_matrix = matrix_utils.add(matrix1, matrix2)
print("\nMatrix 1 + Matrix 2:")
matrix_utils.print(sum_matrix)

-- Matrix transpose
local transposed = matrix_utils.transpose(matrix1)
print("\nMatrix 1 transposed:")
matrix_utils.print(transposed)

-- Create identity matrix
local identity = matrix_utils.create(3, 3, 0)
for i = 1, 3 do
    identity[i][i] = 1
end
print("\n3x3 Identity matrix:")
matrix_utils.print(identity)

-- 3D array example (simple)
local cube = {}
for i = 1, 2 do
    cube[i] = {}
    for j = 1, 2 do
        cube[i][j] = {}
        for k = 1, 2 do
            cube[i][j][k] = i * 100 + j * 10 + k
        end
    end
end

print("\n3D array example:")
for i = 1, 2 do
    print("Layer " .. i .. ":")
    for j = 1, 2 do
        local row = ""
        for k = 1, 2 do
            row = row .. cube[i][j][k] .. " "
        end
        print("  " .. row)
    end
end

Expected Output:

2D Array (Matrix) operations:
Matrix 1:
   1    2    3 
   4    5    6 
   7    8    9 

Matrix 2:
   9    8    7 
   6    5    4 
   3    2    1 

Matrix 1 + Matrix 2:
  10   10   10 
  10   10   10 
  10   10   10 

Matrix 1 transposed:
   1    4    7 
   2    5    8 
   3    6    9 

3x3 Identity matrix:
   1    0    0 
   0    1    0 
   0    0    1 

3D array example:
Layer 1:
  111 112 
  121 122 
Layer 2:
  211 212 
  221 222 

Sorting Arrays

Built-in and custom sorting algorithms:

-- Sorting examples
local sort_utils = {}

-- Bubble sort (for educational purposes)
function sort_utils.bubble_sort(arr)
    local n = #arr
    local sorted = {}
    
    -- Copy array
    for i = 1, n do
        sorted[i] = arr[i]
    end
    
    -- Bubble sort algorithm
    for i = 1, n - 1 do
        for j = 1, n - i do
            if sorted[j] > sorted[j + 1] then
                sorted[j], sorted[j + 1] = sorted[j + 1], sorted[j]
            end
        end
    end
    
    return sorted
end

-- Built-in sort with custom comparisons
local numbers = {64, 34, 25, 12, 22, 11, 90}
local words = {"banana", "apple", "cherry", "date"}
local people = {
    {name = "Alice", age = 30},
    {name = "Bob", age = 25},
    {name = "Charlie", age = 35}
}

print("Sorting examples:")
print("Original numbers:", table.concat(numbers, ", "))

-- Sort copy using built-in sort
local sorted_numbers = {}
for i, v in ipairs(numbers) do sorted_numbers[i] = v end
table.sort(sorted_numbers)
print("Sorted (ascending):", table.concat(sorted_numbers, ", "))

-- Sort in descending order
local desc_numbers = {}
for i, v in ipairs(numbers) do desc_numbers[i] = v end
table.sort(desc_numbers, function(a, b) return a > b end)
print("Sorted (descending):", table.concat(desc_numbers, ", "))

-- Bubble sort comparison
local bubble_sorted = sort_utils.bubble_sort(numbers)
print("Bubble sort result:", table.concat(bubble_sorted, ", "))

-- Sort strings
local sorted_words = {}
for i, v in ipairs(words) do sorted_words[i] = v end
table.sort(sorted_words)
print("Sorted words:", table.concat(sorted_words, ", "))

-- Sort by length
local by_length = {}
for i, v in ipairs(words) do by_length[i] = v end
table.sort(by_length, function(a, b) return #a < #b end)
print("Sorted by length:", table.concat(by_length, ", "))

-- Sort complex objects
local sorted_people = {}
for i, v in ipairs(people) do sorted_people[i] = v end
table.sort(sorted_people, function(a, b) return a.age < b.age end)
print("People sorted by age:")
for _, person in ipairs(sorted_people) do
    print("  " .. person.name .. " (" .. person.age .. ")")
end

Expected Output:

Sorting examples:
Original numbers:	64, 34, 25, 12, 22, 11, 90
Sorted (ascending):	11, 12, 22, 25, 34, 64, 90
Sorted (descending):	90, 64, 34, 25, 22, 12, 11
Bubble sort result:	11, 12, 22, 25, 34, 64, 90
Sorted words:	apple, banana, cherry, date
Sorted by length:	date, apple, banana, cherry
People sorted by age:
  Bob (25)
  Alice (30)
  Charlie (35)

Common Pitfalls

  • 1-indexing: Lua arrays start at index 1, not 0
  • Sparse arrays: Arrays with gaps can cause unexpected behavior with #
  • ipairs vs pairs: Use ipairs for arrays, pairs for general tables
  • Table modifications during iteration: Can cause skipped elements or errors
  • Reference vs copy: Array assignments create references, not copies

Checks for Understanding

  1. What index does the first element of a Lua array have?
  2. How do you add an element to the end of an array?
  3. What's the difference between table.insert(arr, val) and arr[#arr + 1] = val?
  4. How do you create a 2D array with 3 rows and 4 columns?
  5. What function should you use to iterate over array elements?
Show answers
  1. Index 1 - Lua arrays are 1-indexed
  2. Use table.insert(arr, value) or arr[#arr + 1] = value
  3. Both do the same thing - add to the end. table.insert is more explicit and readable
  4. Create nested loops: for i=1,3 do arr[i]={} for j=1,4 do arr[i][j]=value end end
  5. Use ipairs() for arrays to ensure proper iteration over consecutive integer keys

Next Steps

Now that you understand arrays and array operations, you're ready to explore iterators and learn advanced iteration patterns for processing collections of data.