English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Lua Iterators

Iterator (iterator) is an object that can be used to traverse part or all of the elements in the standard template library container, each iterator object represents a fixed address in the container.

In Lua, an iterator is a structure that supports pointer types, which can traverse each element of the collection.

Generic for iterator

The generic for iterator internally saves the iteration function, actually it saves three values: iteration function, status constant, control variable.

The generic for iterator provides the collection's key/The value pair, the syntax format is as follows:

for k, v in pairs(t) do
    print(k, v)
end

In the above code, k, v are variable lists; pairs(t) is an expression list.

Check the following examples:

array = {"Google", "w3codebox}
for key, value in ipairs(array) 
do
   print(key, value)
end

The output result of the above code is:

1  Google
2  w3codebox

In the above example, we used the default iteration function ipairs provided by Lua.

Let's take a look at the execution process of the generic for:

  • First, initialization, calculate the value of the expression after in, the expression should return three values required by the generic for: iteration function, state constant, control variable; like multi-value assignment, if the number of returned results from the expression is less than three, it will be automatically supplemented with nil, and the extra part will be ignored.

  • Second, call the iteration function with the state constant and control variable as parameters (note: for the for structure, the state constant is not useful, it is only used to get its value and pass it to the iteration function during initialization).

  • Third, assign the values returned by the iteration function to a variable list.

  • Fourth, if the first returned value is nil, the loop ends, otherwise, execute the loop body.

  • Fifth, call the iteration function again at step two

In Lua, we often use functions to describe iterators, and each call to the function returns the next element of the set. Lua iterators include the following two types:

  • Stateless iterator

  • Multi-state iterators

Stateless iterator

A stateless iterator is an iterator that does not retain any state, so we can use a stateless iterator in a loop to avoid the extra cost of creating a closure.

For each iteration, the iteration function is called with the values of two variables (a constant state and a control variable) as parameters, and a stateless iterator can use these two values to obtain the next element.

A typical simple example of a stateless iterator is ipairs, which traverses each element of an array.

In the following example, we use a simple function to implement an iterator to calculate the square of a number n:

function square(iteratorMaxCount, currentNumber)
   if currentNumber < iteratorMaxCount
   then
      currentNumber = currentNumber+1
   return currentNumber, currentNumber*currentNumber
   end
end
for i, n in square,3,0
do
   print(i, n)
end

The output result of the above example is:

1    1
2    4
3    9

The state of iteration includes the table being traversed (a constant state that does not change during the loop process) and the current index (a control variable), the ipairs and iteration functions are very simple, and we can implement them in Lua as follows:

function iter(a, i)
    i = i + 1
    local v = a[i]
    if v then
       return i, v
    end
end
 
function ipairs(a)
    return iter, a, 0
end

When Lua calls ipairs(a) to start the loop, it gets three values: the iteration function iter, the state constant a, and the control variable initial value 0; then Lua calls iter(a,0) to return 1, a[1] (unless a[1=nil); The second iteration calls iter(a,1) return 2, a[2...... until the first nil element.

Multi-state iterators

In many cases, iterators need to save multiple state information rather than simple state constants and control variables. The simplest method is to use closures, and another method is to encapsulate all state information into a table, using the table as the iterator's state constant. Because all information can be stored in the table in this case, the iteration function usually does not need a second parameter.

In the following example, we create our own iterator:

array = {"Google", "w3codebox}
function elementIterator(collection)
   local index = 0
   local count = #collection
   -- Closure Function
   return function ()
      index = index + 1
      if index <= count
      then
         --  Return the current element of the iterator
         return collection[index]
      end
   end
end
for element in elementIterator(array)
do
   print(element)
end

The output result of the above example is:

Google
w3codebox

In the above examples, we can see that the closure function is used inside elementIterator to calculate the size of the collection and output each element.