[Include] i_foreach - Small and lightweight foreach with custom iterators
#1

i_foreach 1.1
Download
About
This is a very small include that adds a new syntactical element to Pawn - the foreach statement. You probably know it from other languages (or y_foreach), the statement is used to iterate over a collection of elements. Unlike y_foreach however, this is merely a syntactic sugar for a fancy for loop, and doesn't contain functions for modifying custom collections. You can, however, create a custom iterator using this, and it also supports returning any number of values, even arrays.

Introduction
In the past, I faced the same thing as anyone who develops a gamemode would eventually face - the repetition of per-player or other loops. Lines like "for(new i = 0; i < MAX_PLAYERS; i++)if(IsPlayerConnected(i))" became numerous, and so I decided to make a simple macro "ForAllPlayers(i)" that would be simply be replaced with the former.

After learning C#, I wondered if maybe the "foreach" statement from it would be portable to Pawn. At that time, I am not sure if I knew YSI or y_foreach, but I decided to make my own simple one. My foreach is extensible, small, and uses no hooks.

Usage
Usage is simple:
Code:
foreach(new player in players)
{
    SetPlayerColor(player, -1);
}
This translates to something like this:
Code:
for(new player = 0, __top_player = GetPlayerPoolSize(); player <= __top_player; player++)if(IsPlayerConnected(player)&&!IsPlayerNPC(player))
{
    SetPlayerColor(player, -1);
}
As you can see, it is simply an elegant syntactic sugar for a common concept, correctly implemented.

You should include this after standard SA-MP libraries, because it checks for availability of some functions.

Iterators
The include provides a number of iterators (like players) for enumerating various collections. The iterator's name is only valid in the foreach statement, and can be used for something else outside of it.

total_players
All connected players, including NPCs.

players
All real players (i.e. not NPCs).

npcs
All connected NPCs.

vehicles
All created vehicles.

objects
All created objects.

pickups
All created pickups.

actors
All created actors.

chars(str)
All character and their indices in a string, which can be specified in str.
Code:
new str[]= "Hello!";
foreach(new i, c in chars(str))
{
    printf("str[%d]=%c", i, c);
}
pvars(plr)
All PVars indices defined for a player, which can be specified in plr. You still have to check if the PVar index is actually valid, because there is no GetPVarTypeFromIndex.

range(a, b)
Iterates over a range of numbers, starting at a and ending at b (inclusive).

Custom iterators
In addition to these pre-defined iterators, you can create your own ones! An iterator has five components: the initial value, the final value, the filter, the values selector, and the name selector (more about these later, you can set them to default). See the example:

Code:
#define iter_initial_total_players 0
#define iter_check_total_players(%0) IsPlayerConnected(%0)
#define iter_top_total_players GetPlayerPoolSize()
#define iter_selector_total_players(%1)(%2) iter_selector_default(%1)(%2)
#define iter_name_total_players(%1)(%2) iter_name_default(%1)(%2)
This defines the total_players iterator. The minimum player ID is 0, and the maximum can be obtained using GetPlayerPoolSize. IsPlayerConnected is used to skip invalid player IDs. If you want the check to always success, you can use iter_check_true, which is set to always true. In this case, the values and name selectors are set to default, but you can specify your own to return multiple values.

Parametrized iterators can be also created; see the code for the "range" iterator:

Code:
#define iter_initial_range(%0,%1) %0
#define iter_top_range(%0,%1) %1
#define iter_check_range(%0,%1)(%2) iter_check_true
#define iter_selector_range(%0,%1)(%2)(%3) iter_selector_default(%2)(%3)
#define iter_name_range(%0,%1)(%2)(%3) iter_name_default(%2)(%3)

foreach(new i in range(5, 15))
{
    printf("%d", i); //prints 5 to 15
}
Multiple and tagged values
The two additional macros, specifying the selectors, allow you to define iterators that return any number of values of any type. To illustrate an iterator with tags, let's try to create an iterator that returns all the powers of 0.5:

Code:
#define iter_initial_halves 0
#define iter_top_halves cellmax
#define iter_check_halves(%1) iter_check_true
#define iter_selector_halves(%1)(Float:%2) %2=(floatpower(0.5,%1))
#define iter_name_halves(%1)(Float:%2) %1%2
This actually iterates over the exponent, and computes the power later. The first three lines should be clear - the exponent starts at 0, goes to "infinity", and every exponent is included. The last two lines are more tricky. First, we need to understand what are these selectors for. The first one is used to compute the actual value obtained from the iterator, because the actual iterating variable is hidden. However, the hidden variables need to have a unique name, therefore their name is obtained from the variables used to hold the obtained values. In order for the macro to determine which part of the variable list should be used for the name, the name selector is necessary.

This is the default implementation of the selectors:
Code:
#define iter_selector_default(%1)(%2) %2=%1
#define iter_name_default(%1)(%2) %1%2
The default value selector simply copies the value of the iterator variable (in %1) to the output variable (%2). The name selector appends the prefix (passed in %1) to the name of the output variable (%2). Used prefixes are __iter_ (for the iterator variable) and __top_ (for the maximum iterated value), and they are generally prepended to the name of the first output variable. If the variable has the Float: tag, it needs to be specified, otherwise the resulting name would be something like "__iter_Float:name" which is wrong, of course.

The value selector need not specify only one assignment; it can do any number of assignments, thanks to the "," operator. See the definition for chars:
Code:
#define iter_initial_chars(%0) 0
#define iter_top_chars(%0) strlen(%0)-1
#define iter_check_chars(%0)(%1) iter_check_true
#define iter_selector_chars(%0)(%1)(%2,%3) %2=%1,%3=%0[%1]
#define iter_name_chars(%0)(%1)(%2,%3) iter_name_default(%1)(%2)
Here, two assignments are done, one to the index variable, and one to the character variable. %0 corresponds with the iterated string, %1 is the iterator variable, and %2 and %3 are the output variables. The name selector uses only the first variable, because it is sufficient.

There are no limits to the kind of output variables you want to use, as long as you specify the value and name selector correctly. You can even return arrays or strings from the iterator!

Code:
#define iter_initial_player_names iter_initial_players
#define iter_top_player_names iter_top_players
#define iter_check_player_names(%1) iter_check_players(%1)
#define iter_selector_player_names(%1)(%2,%3[MAX_PLAYER_NAME]) %2=%1,GetPlayerName(%1,%3,MAX_PLAYER_NAME)
#define iter_name_player_names(%1)(%2,%3[MAX_PLAYER_NAME]) %1%2

foreach(new plr, name[MAX_PLAYER_NAME] in player_names)
{
    printf("%s (%d)", name, plr);
}
This iterates over all players, and automatically obtains their names. The first part of the value selector (%2=%1) again simply copies the iterator variable, and the second part calls GetPlayerName on it, passing it the second output variable. The size of the array need not be specified (replace MAX_PLAYER_NAME with %4, but in this case, it is a good practice to validate the size this way.

Download
At the top of this topic.

Changelog
1.1.1 - Space before new is now allowed.
1.1 - Added the range iterator, chars iterator modified to also yield the character values, and added the support for multiple and tagged values and arrays. The macro definition is also more benevolent to spacing in the foreach statement.
1.0 - Initial version.
Reply
#2

Thanks for releasing.
Reply
#3

no this isnt how foreach works.
Reply
#4

Quote:
Originally Posted by CodeStyle175
View Post
no this isnt how foreach works.
In its essence, common to all languages that use it, foreach iterates over a collection. In this case, the "collection" is represented using indices defined with a set of macros, but how does it contradict the principle of foreach?
Reply
#5

Version 1.1 is released, supporting multiple output variables returned from the iterator, tagged variables, and arrays and strings (or the combination of all).
Reply
#6

As ****** once said and I Confirm, Ur this version looks poor than previous one xd
Reply
#7

Quote:
Originally Posted by OmerKhan
View Post
As ****** once said and I Confirm, Ur this version looks poor than previous one xd
If he allready said that? why do you repeat??
Reply
#8

yes thats some quality code over here
PHP Code:
#define foreach(new%9\32;%0\32;%8in%7\32;%1) for(new %9%0%8, iter_name_%1(__iter_)(%9%0%8) = iter_initial_%1, iter_name_%1(__top_)(%9%0%8) = iter_top_%1; iter_name_%1(__iter_)(%9%0%8) <= iter_name_%1(__top_)(%9%0%8); iter_name_%1(__iter_)(%9%0%8)++)if(iter_check_%1(iter_name_%1(__iter_)(%9%0%8))&&(_:!_:(iter_selector_%1(iter_name_%1(__iter_)(%9%0%8))(%9%0%8))+1)) 
Reply
#9

Thanks for your observations. I was aware of the spacing issues; there should indeed be no space after new or in, and I am sorry for not making enough tests on the new version (I had limited time and wanted to release it as quickly as possible). Sadly, space detection in Pawn is quite quirky, and I don't have complete understanding of the preprocessor to make it more flexible in this way. The inability to place "i" in the second variable name is surprising; it looks to me like the compiler could do a better job at attempting to recognize the macro.

I agree it is not very flexible, but I originally made this only for my personal use, tailored to my coding style and preferences (spacing). At least the second version doesn't break anything that was possible with the first version (or I am not aware of it).

Minor version 1.1.1 was released to allow a space before "new" (some coding styles have that). I hope it doesn't break something else.

Quote:
Originally Posted by CodeStyle175
View Post
yes thats some quality code over here
PHP Code:
#define foreach(new%9\32;%0\32;%8in%7\32;%1) for(new %9%0%8, iter_name_%1(__iter_)(%9%0%8) = iter_initial_%1, iter_name_%1(__top_)(%9%0%8) = iter_top_%1; iter_name_%1(__iter_)(%9%0%8) <= iter_name_%1(__top_)(%9%0%8); iter_name_%1(__iter_)(%9%0%8)++)if(iter_check_%1(iter_name_%1(__iter_)(%9%0%8))&&(_:!_:(iter_selector_%1(iter_name_%1(__iter_)(%9%0%8))(%9%0%8))+1)) 
Thanks for the compliment!
Reply
#10

Quote:

My foreach is extensible, small, and uses no hooks.

Before hand, saying that I haven't fully read the first post.

But what interests me, is if I can squeeze that lemon more with this include over y_iterate and save some processing/time (benchmarks in other words)

And those custom iterators ( )
Quote:

#define iter_initial_total_players 0
#define iter_check_total_players(%0) IsPlayerConnected(%0)
#define iter_top_total_players GetPlayerPoolSize()
#define iter_selector_total_players(%1)(%2) iter_selector_default(%1)(%2)
#define iter_name_total_players(%1)(%2) iter_name_default(%1)(%2)

That doesn't sound much of an advantage in simplicity of writing a code.
Reply
#11

does it make code better, when it looks some hardcore shit, but it just simple and doesnt have any special functionalities?
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)