[Tutorial] Advanced Iterators (foreach/y_iterate).
#3

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

Special Iterators

By now you should have a pretty firm grasp on standard iterators. In terms of
how they are defined, used, and how they work internally.

pawn Code:
// Declare the iterator:
new
    Iterator:MyIter<55>;
// Add any value between 0 and 54 to the iterator:
Iter_Add(MyIter, 0);
Iter_Add(MyIter, 42);
Iter_Add(MyIter, 54);
// Use the iterator:
foreach (new i : MyIter)
{
    print("%d", i);
}
Output:

pawn Code:
0
4
6
This is how the "Player" iterator is defined and runs, as are many others.
However, there is another set of iterators, used indistinguishably from this
first set, but defined in a very different way - these are called "special
iterators".

Example 1

Lets say you want to write an iterator to loop through all positive even
numbers. You could try do this:

pawn Code:
#define IsOdd(%0)  ((%0) & 1)
#define IsEven(%0) (!IsOdd(%0))

new
    Iterator:EvenInt<cellmax>;
for (new i = 0; i != cellmax; ++i)
{
    if (IsEven(i))
    {
        Iter_Add(EvenInt, i);
    }
}
foreach (new j : EvenInt)
{
    printf("%d", j);
}
Output:

pawn Code:
0
2
4
...
At first glance that seems OK. We loop through every integer and add only the
even ones. But there are two issues Firstly the "Iterator:" macro adds on an
extra cell, so we end up with "EvenInt@YSII_Ag[cellmax + 1]". "cellmax" is the
highest possible integer value, so adding 1 to it is an invalid operation and
won't compile. But assuming that it is possible to do that, we would still end
up with an array consisting of 2147483648 4-byte cells - that's exactly 8Gb of
data in your compiled mode! Again, this won't compile.

Fortunately there is another way in the form of a special iterator function:

pawn Code:
#define EvenInt@YSII_Ag%0- @iterfunc EvenInt()
stock EvenInt@YSII_Ag(cur)
{
    switch (cur)
    {
        case -1: return 0;
        case cellmax - 1: return -1;
    }
    return cur + 2;
}
As before we can now do:

pawn Code:
foreach (new i : EvenInt)
{
    printf("%d", j);
}
Output:

pawn Code:
0
2
4
...
The important difference is that this will compile and run, and won't be vast!

Explanation

Unfortunately the syntax for special iterators is a little clumsy so I'll
explain it very quickly:

pawn Code:
#define EvenInt@YSII_Ag%0- @iterfunc EvenInt()
This line declares a function as a special iterator. The syntax for the line is
always the same, regardless of the number of parameters to your iterator
function:

pawn Code:
#define IterName@YSII_Ag%0- @iterfunc IterName()
Notice the hyphen after "%0" - without that your code won't compile. The
spacing is also important here - don't try write "IterName ()" for example.

pawn Code:
stock EvenInt@YSII_Ag(cur)
This line actually starts ths special iterator function. "stock" is simply
because you don't know if your iterator will be used or not and you don't want a
warning (unless you have it in your mode and know you will use it, in which case
don't use "stock"). The next part is the function name, which again is:
"IterName@YSII_Ag". The "@YSII_Ag" suffix is a leftover from the standard
iterators.

pawn Code:
case -1: return 0;
Standard iterators compile to something similar to:

pawn Code:
for (new i = SIZE_MINUS_1; (i = ITER[i]) != SIZE_MINUS_1; )
For special iterators, there is no size, so there is no start or end point using
this scheme. Instead, "-1" is used:

pawn Code:
for (new i = -1; (i = FUNC(i)) != -1; )
As a side note, this won't work for regular iterators because "-1" is not a
valid array index, so can't be used as the start value without a significant
loss in efficiency.

Anyway: "-1" is passed to the special iterator function at the start of the loop
to get the first value. Here the first value is the first positive even
integer, i.e. 0, so we return that.

pawn Code:
case cellmax - 1: return -1;
As with "-1" as an input being the start of a special iterator, "-1" as a return
value marks the end of the special iterator. Note that this does mean that no
special iterators can ever have "-1" as a valid return unfortunately... In this
case, we need to return "-1" when we run out of positive even integers. The
last number available in signed 32bit integers is "2147483647" - defined in PAWN
as "cellmax", but this is odd so the last positive integer must be 1 less than
that, i.e. "2147483646", which can be written out in full or calculated as
"cellmax - 1". Therefore, when the input to the special iterator is the last
possible positive even integer there can be no further valid returns and instead
"-1" is returned to mark the end of the loop.

pawn Code:
return cur + 2;
This is very simple. For all other input numbers, return the next even number
after that - defined as "cur + 2".

Example 2

An odd numbers iterator would be almost identical, just with different start and
end values:

pawn Code:
#define OddInt@YSII_Ag%0- @iterfunc OddInt()
stock OddInt@YSII_Ag(cur)
{
    switch (cur)
    {
        case -1: return 1;
        case cellmax: return -1;
    }
    return cur + 2;
}
"@iterfunc" is a macro defined in "y_iterate", but we don't actually USE it here
- remember that "#define" creates a text-based replacement so that macro will
only be USED when "foreach" is used with "OddInt":

pawn Code:
foreach (new c : OddInt)
{
}
The fact that no macros from "y_iterate" are actually called here means that we
can DEFINE a special iterator without including "y_iterate", and can thus leave
that decision up to the USER of the library. If they never use the special
iterator and don't include "y_iterate" then because we never include it either
no excess code is generated.

Example 3

Lets say you want to write an iterator to loop through all currently connected
RCON admins. Resulting in the equivalent of:

pawn Code:
foreach (new playerid : Player) if (IsPlayerAdmin(playerid))
{
}
A first attempt might try to use standard iterators and hook
"OnRconLoginAttempt". This is possible, but not as easy as it would first
appear due to "OnRconLoginAttempt" not giving a playerid and the chance that
multiple players may share an IP:

pawn Code:
new
    Iterator:Admin<MAX_PLAYERS>;

hook OnRconLoginAttempt(ip[], password[], success)
{
    // We can't just access the logged in player directly.
    if (success)
    {
        // Rebuild the iterator.
        foreach (new playerid : Player)
        {
            if (IsPlayerAdmin(playerid) && !Iter_Contains(Admin, playerid))
            {
                // Add this admin not already in the array.
                Iter_Add(Admin, playerid);
            }
        }
    }
}

hook OnPlayerDisconnect(playerid, reason)
{
    if (Iter_Contains(Admin, playerid))
    {
        Iter_Remove(Admin, playerid);
    }
}
We can now do:

pawn Code:
foreach (new admin : Admin)
{
    // Loop over all the RCON admins.
}
Indeed, this is an acceptable solution (and it turns out this is actually the
BEST solution in terms of speed). However, there is an alternative route that
can be taken which is much simpler to write and avoids hooking callbacks:

pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
    // Loop over all remaining players AFTER the current player.
    for (new i = cur + 1; i < MAX_PLAYERS; i++)
    {
        // Test if they are an admin (includes an "IsPlayerConnected" check).
        if (IsPlayerAdmin(i))
        {
            return i;
        }
    }
    // Generic "foreach" failure is "-1".
    return -1;
}
The first thing to note is that the "-1" input is never explicitly mentioned.
Instead it is handled generically by "i = cur + 1", which makes "i == 0" when
"cur == -1", and that is the correct initial value for looping over players.

The syntax to use this new special iterator version is identical to the syntax
for the original iterator version. The benefit to this being that the
implementation can be swapped out in your library without users having to
adjust their code at all:

pawn Code:
foreach (new admin : Admin)
{
    // Loop over all the RCON admins, using the special iterator.
}
Reply


Messages In This Thread
Advanced Iterators (foreach/y_iterate). - by Ada32 - 14.04.2015, 19:43
Advanced Iterators (foreach/y_iterate) - Part 2 - by Ada32 - 14.04.2015, 19:45
Advanced Iterators (foreach/y_iterate) - Part 3 - by Ada32 - 14.04.2015, 19:47
Re: Advanced Iterators (foreach/y_iterate). - by Pottus - 14.04.2015, 19:49
Advanced Iterators (foreach/y_iterate) - Part 4 - by Ada32 - 14.04.2015, 19:50
Advanced Iterators (foreach/y_iterate) - Part 5 - by Ada32 - 14.04.2015, 19:52
Re: Advanced Iterators (foreach/y_iterate). - by Crayder - 02.05.2015, 07:16
Re: Advanced Iterators (foreach/y_iterate). - by Konstantinos - 02.05.2015, 10:50
Re: Advanced Iterators (foreach/y_iterate). - by Gammix - 10.04.2018, 00:08

Forum Jump:


Users browsing this thread: 1 Guest(s)