Garry's Mod Wiki

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⤶ ```