Revision Difference
iterators#567552
<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.⤶
⤶
## 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⤶
```⤶
⤶
# 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⤶
⤶
--- 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 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