Revision Difference
iterators#567555
<cat>Dev.Lua</cat>
<title>Concepts - Iterators</title>
# What is an iterator?
Very simply, an iterator is a function that "iterates" over a certain thing using a `for` block until it cannot anymore! An example of an iterator that you'll learn quickly is the <page>Global.pairs</page> iterator function.
Very simply, an iterator is a function that gets the next value in a set of values for use in a `for` block until it cannot anymore! An example of an iterator that you'll learn quickly is the <page>Global.pairs</page> iterator function.
## Example
```lua
local some_table = {
1, 2, nil, 4, 5
}
for k, v in pairs(some_table) do
print(v)
end
---
--- Outputs:
--- 1
--- 2
--- 4
--- 5
```⤶
⤶
## Unwrapping pairs⤶
To help better understand what `for` actually does internally, this is an example of unwrapping pairs into a `while` loop. In a `for` loop, everything except operating on the values themselves is handled for you, as seen below.⤶
```lua⤶
local t = {"a", "b", "c", "d", "e"}⤶
⤶
--- This is essentially what a for loop does internally⤶
--- First, it gets the values returned from your iterator function⤶
--- This should always be a function, the data and key (control)⤶
local fn, data, k = pairs(t)⤶
⤶
--- Next, we call the returned function until we cant anymore⤶
while true do⤶
local v⤶
⤶
--- Next, since we assume they key is the first return of⤶
--- the returned function, we override what pairs⤶
--- thought the initial one was⤶
k, v = fn(data, k)⤶
⤶
if not k then break end⤶
⤶
--- Lastly, we actually do something with the values here⤶
--- Same as between do and end in a for loop⤶
print(k, v)⤶
end⤶
⤶
--- Output:⤶
--- 1 a⤶
--- 2 b⤶
--- 3 c⤶
--- 4 d⤶
--- 5 e⤶
```
# How do I write my own?
Iterators work through a little bit of `for` magic and a <page>function</page> that returns another <page>function</page>. They are divided into two steps.
Lets say for this example, you want to iterate over every character in a <page>string</page>
## Step 1 - Initialization
First, we define our generator <page>function</page> that will act as our iterator, like `pairs`!
```lua
function strchars(str) --- This is our "wrapper" or "generator" function, what generates new iterations
--- Our work goes here
end
```
Next, we have two ways of going about things, we can either handle our own "state" or allow Lua to handle the iteration state for us.
<note>State here is relating to the data we store to remember where the iterator currently is</note>
## Step 2.a - Handling our own state
If we want to handle our own state, we define it in our wrapper function locally as such.
Handling our own state comes with some benefits and some cons, one of the biggest benefits is our state doesn't have to be the first value returned by our generator.
```lua
function strchars(str)
--- First, we declare all our state
local index = 1 --- String indices start at [1], so we start here
--- Next, we return a new function that will get us our data
--- Return values from this are passed to the arguments in our for loop
--- If we return nil, the loop will be stopped immediately
return function()
index = index + 1
if str[index] == "" then -- Since indexing a string never returns nil, this is checking if we've reached the end
return
end
--- Notice that here we're returning the value first and index second!
--- This is reversed from how pairs() and ipairs() does it
--- We can only do this with managing our own state
return str[index], index
end
end
for char, i in strchars("abc") do
print(char, i)
end
---
--- Outputs:
--- a 1
--- b 2
--- c 3
```
## Step 2.b - Lua handling our state
If we want to let Lua handle our state for us, we need to remember that the first value returned from our generator must be our state, otherwise it will be discarded.
```lua
function strchars(str)
--- Here, we declare nothing in the wrapper and just generate a new function
--- Lua assumes that the first returned value from our generator function is our state value
--- so it has features built in to make this easier
return function(_, index) --- The first argument passed here is elaborated on in "Esoteric Information", it is our invariant state
index = index + 1
if str[index] == "" then return end
--- Unlike the previous function, were returning the index first since thats what Lua is expecting to be the state control value
--- The first value returned here gets passed to this function as index
return index, str[index]
end
end
for i, char in strchars("abc") do
print(i, char)
end
---
--- Outputs:
--- 1 a
--- 2 b
--- 3 c
```
# Esoteric Information
There are extra values that are able to be passed after the iterator function call in `for in` loops.
## "Invariant State"
Following the iterator function in a `for in` loop, you can provide something referred to as the ["invariant state"](https://www.lua.org/pil/7.2.html). This is essentially the **constant** (as in "invariant") data to be passed as the first argument to the function the iterator generator returns.
This is useful for a couple reasons, primarily, we can skip creating a generator entirely if we have a well defined function.
```lua
--- Since this function is built for being called directly and not from a generator
--- We accept two arguments, some data, and some index
--- This function is practically identical to the generated code in Step 2.b, except that we let Lua handle our data as well as our state index
function strchars(str, index)
index = index or 0 --- Index starts nil, so we default it to 0
index = index + 1 --- Next, we add one to it
if str[index] == "" then return end --- Cancel once were done
return index, str[index]
end
--- Instead of calling a function for the first value, we directly use our unwrapped generator function
--- We also provide a second expression after strchars, this is our invariant state
for i, ch in strchars, "abc" do
print(i, ch)
end
---
--- Outputs:
--- 1 a
--- 2 b
--- 3 c
```
## "Control Variable"
After our invariant state, we can provide a "control variable", this is our initial index variable!
```lua
--- Note that were using strchars from "Invariant State"
--- Like above, we call our unwrapped generator function with some invariant state of a string
--- However, we also pass another, final parameter of 1
--- This is passed into strchars as our initial index upon the first call, essentially where we start
for i, ch in strchars, "abc", 1 do
print(i, ch)
end
---
--- Outputs:
--- 2 b
--- 3 c
```
Garry's Mod
Rust
Steamworks
Wiki Help