PDA

Просмотр полной версии : [Урок] Итераторы в Pawn



VVWVV
24.03.2017, 02:35
foreach - одна из библиотек проекта YSI (y_iterate), добавляющая возможность создавать собственные итераторы (https://ru.wikipedia.org/wiki/%D0%98%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80). Из-за особенностей алгоритма процесс итерирования может проходить намного быстрее простого перебора. Помимо этого, библиотека имеет обширный функционал.

Как было сказано выше, библиотека является частью проекта YSI, но, тем не менее, существует версия без зависимости от YSI. Отличие заключается лишь в том, что на данный момент только версия из YSI поддерживается самим разработчиком.

Ограничения и особенности алгоритма

Некоторые ограничения:

В итератор не могут входить числа меньше нуля и больше, чем размер итератора.
В итераторе все числа идут по порядку, кроме самого меньшего (он расположен в самом конце итератора)


Алгоритм и его особенности
Дело в том, что при инициализации итератора вы создаёте всегда на одну ячейку больше. Эта ячейка будет содержать размер итератора (не всегда, см. выше); она также помогает определить границу итератора при итерировании.

Итерирование происходит так: сначала читается последний элемент, если он не равен размеру итератора, то значение в ячейке записывается. Затем происходит ещё одна итерация, теперь уже сравнивается размер и значение элемента массива с индексом записанного числа и так много раз, пока значение элемента не будет равно размеру итератора. Поэтому вы не можете указывать числа больше размера итератора или меньше нуля, т.к. это нарушит алгоритм и вызовет ошибки.

http://ihost.pro-pawn.ru/image.php?di=85FQ

Как видим, все остальные ячейки заполняются числами больше размера массива, это делается для того, чтобы поместить в последнюю ячейку именно размер.


Создание итераторов

Обычный итератор

Для создания такого итератора необходимо знать только шаблон макроса.

Iterator:name<10>

name - имя итератора.
10 - размер итератора.


Предостережения:

Макрос на самом деле генерирует сразу две переменные: переменную с количеством действующих элементов, а также сам массив.
Массив заполняется значениями при его декларировании, следовательно, вы не сможете установить собственные значения.
К имени прибавляется суффиксы "@YSII_Cg" для переменной с количеством элементов и "@YSII_Ag" для массива.


Многомерные итераторы

Для создания многомерных итераторов необходимо знать не только шаблон, но и функцию заполнения, поскольку массивы не могут быть инициализированы под алгоритм во время компиляции.

Рассмотрим шаблон:

IteratorArray:name[12]<10>

name - имя итератора.
12 - размер первого итератора.
10 - размер второго итератора.


Предостережения:

Макрос на самом деле генерирует сразу две переменные: массив с количеством действующих элементов для каждого массива, а также сам многомерный массив.
Необходимо инициализировать функцией.
К имени прибавляется суффиксы "@YSII_Cg" для переменной с количеством элементов и "@YSII_Ag" для массива.

Пример инициализации:


new IteratorArray:PlayerObjects[MAX_PLAYERS]<10>;

public OnGameModeInit() {
Iter_Init(PlayerObjects);
}



Документация и использование итераторов

Документация к функциям
В библиотеке присутствует множество разнообразных функций, помогающих в работе с итераторами.


Название
Описание


Iter_Init
Инициализирует многомерные итераторы.

Параметры:

name - имя итератора

Возвращает:

Всегда нуль.


Iter_Add
Добавляет новое значение в итератор.

Параметры:

name - имя итератора
value - добавляемое значение

Возвращает:

В случае успеха возвращает истину, иначе ложь.


Iter_Free
Находит свободный слот в итераторе.

Параметры:

name - имя итератора

Возвращает:

Индекс свободного слота.


Iter_Remove
Удаляет значение из итератора

Параметры:

name - имя итератора
value - удаляемое значение

Возвращает:

В случае успеха возвращает истину, иначе ложь.


Iter_Contains
Проверяет наличие значения в итераторе

Параметры:

name - имя итератора
value - проверяемое значение

Возвращает:

В случае успеха возвращает истину, иначе ложь.


Iter_SafeRemove
Безопасно удаляет значение из итератора.

Параметры:

name - имя итератора
value - удаляемое значение
next - индекс на следующий элемент.

Возвращает:

В случае успеха возвращает истину, иначе ложь.


Iter_Random
Случайно определяет число из итератора.

Параметры:

name - имя итератора

Возвращает:

Случайное число из итератора


Iter_Count
Количество добавленных элементов.

Параметры:

name - имя итератора

Возвращает:

Количество добавленных элементов.


Iter_Clear
Освобождает итератор от всех значений.

Параметры:

name - имя итератора

Возвращает:

Всегда нуль.


Iter_First
Получает первый элемент в итераторе.

Параметры:

name - имя итератора

Возвращает:

Возвращает индекс.


Iter_Last
Получает последний элемент в итераторе.

Параметры:

name - имя итератора

Возвращает:

Возвращает индекс.


Iter_Next
Получает следующий элемент в итераторе.

Параметры:

name - имя итератора
current - текущий индекс.

Возвращает:

Возвращает следующий индекс.


Iter_Prev
Получает предыдущий элемент в итераторе.

Параметры:

name - имя итератора
current - текущий индекс.

Возвращает:

Возвращает следующий индекс.


Iter_InternalArray
Получает доступ к массиву.

Параметры:

name - имя итератора.

Возвращает:

-


Iter_InternalSize
Получает действительный размер массива.

Параметры:

name - имя итератора.

Возвращает:

Размер массива.


Созданные итераторы
В библиотеке присутствуют уже созданные итераторы такие как: Player, Vehicle, Bot, NPC (то же самое, что и Bot), Character (NPC и игроки). Поэтому вам не придётся создавать их снова.

Псевдооператор foreach
Именно с помощью данного оператора и происходит итерирование. Данный оператор схож с range-based циклами в С++11. Однако у foreach есть и старая версия синтаксиса, которую не рекомендуют использовать. Кстати, в новой версии библиотеки она всё ещё присутствуют, но, тем не менее, при использовании появляется предупреждение, говорящее как раз об этом. Поэтому в данном уроке его не будет.

Рассмотрим синтаксис:


foreach(new i : Player)



new - спецификатор, говорящий о том, что необходимо создать новую переменную.
i - название переменной, в которую будет записываться значение из итератора. Оно может быть абсолютно любое.
Player - название итератора, из которого мы будет брать значения.



Примеры использования
Пример №1
Допустим, что нам нужно сделать определённый массив, в котором будут храниться идентификаторы администраторов. С помощью этого массива мы, например, будет отправлять сообщения всем администраторам. Как раз для этого и необходимы итераторы.

Создадим итератор, например, с названием ConnectedAdmins.

new Iterator:ConnectedAdmins<MAX_PLAYERS>;

Далее создадим функцию отправки сообщения этим администраторам:


stock SendAdminMessage(adminid, color, const message[])
{
foreach(new i : ConnectedAdmins) {
SendClientMessage(i, color, message);
}
}


Теперь нам остаётся сделать только добавление/удаление идентификаторов в итератор при входе/выходе администратора.

Пример №2
Итак. Суть данного примера заключается в том, что мы должны вывести цифры в обратном порядке, т.е. сделать простой обратный цикл.

Создадим итератор, например, с названием Nums. Он будет содержать, допустим, 10 ячеек.

new Iterator:Nums<10>;

В библиотеки не оказалось обратного цикла, поэтому мы сделаем свой:


stock PrintNums() {
for (new i = Iter_End(Nums); (i = Iter_Prev(Nums, i)) != Iter_Begin(Nums);)
printf("%d", i);
}

Нам осталось лишь загрузить значения и скомпилировать.

Пример №3
Создадим скрипт, суммирующий счёт каждой команды. В нём нам необходимо использовать массива итераторов.

#define MAX_TEAMS 255

new
Iterator:Team[MAX_TEAMS]<MAX_PLAYERS>;

public OnGameModeInit()
{
Iter_Init(Team);

#if defined Team_OnGameModeInit
return Team_OnGameModeInit();
#else
return 1;
#endif
}
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif

#define OnGameModeInit Team_OnGameModeInit
#if defined Team_OnGameModeInit
forward Team_OnGameModeInit();
#endif



stock Team_SetPlayerTeam(playerid, teamid)
{
new current_team = GetPlayerTeam(playerid);
if (current_team != NO_TEAM) {
Iter_Remove(Team[current_team], playerid);
}

if (teamid != NO_TEAM) {
Iter_Add(Team[teamid], playerid);
}

return SetPlayerTeam(playerid, teamid);
}
#if defined _ALS_SetPlayerTeam
#undef SetPlayerTeam
#else
#define _ALS_SetPlayerTeam
#endif

#define SetPlayerTeam Team_SetPlayerTeam

Использовать так:

new scores[MAX_TEAMS];
for (new teamid; teamid < MAX_TEAMS; teamid++) {
foreach (new playerid : Team[teamid]) {
scores[teamid] += GetPlayerScore(playerid);
}
}

for (new teamid; teamid < MAX_TEAMS; teamid++) {
printf("Team %d: %d", teamid, scores[teamid]);
}


Разные версии

Существует две версии данной библиотеки:

foreach - автономная версия, не зависящая от других инклудов. Если вы не используете YSI, то можете смело скачивать эту версию. Автор (Y_Less) отказался поддерживать этот инклуд, поэтому форки инклуда есть сразу у нескольких представителей сообщества SA-MP - на момент написания наиболее актуальный форк можно найти у ziggi.
Отличается данная версия наличием итераторов для актёров и транспорта (в том числе итераторов Stream, которые выключены по умолчанию), поддержкой множественных скриптов (FS + GM), оптимизированным ALS (без использования CallLocalFunction) и отсутствием поддержки npcmodes (для более чистого кода).

y_iterate - это версия, поддерживаемая самим разработчиком YSI. Однако, чтобы использовать её, необходимо скачать полностью весь проект YSI, ибо она привязана к другим инклудам. Таким образом, если вы используете YSI в своём проекте, то вам больше подойдёт y_iterate.



Скачать:
foreach - https://github.com/Open-GTO/foreach
y_iterate - https://github.com/Misiur/YSI-Includes

Для скачивания на открывшейся странице нажмите на кнопку "Clone or download" (зелёного цвета) и в открывшемся меню - "Download ZIP".


Статью подготовил: VVWVV (http://pro-pawn.ru/member.php?4348)


Исключительно для pro-pawn.ru
Копирование данной статьи на других ресурсах без разрешения автора запрещено!

ziggi
24.03.2017, 07:48
Предложу свой форк standalone библиотеки: https://github.com/Open-GTO/foreach/releases
Отличается наличием итераторов для актёров и транспорта (в том числе Stream итераторов, которые выключены по умолчанию), поддержкой множественных скриптов (FS + GM), оптимизированным ALS (без использования CallLocalFunction) и отсутствием поддержки npcmodes (для более чистого кода).

P.S. Не увидел примера использования массивов итераторов, предложу свой: https://github.com/Open-GTO/foreach#custom-array-of-iterators
Массив итераторов с игроками в команде:

#define MAX_TEAMS 255
#define MAX_PLAYERS_IN_TEAM MAX_PLAYERS / MAX_TEAMS

new
Iterator:Team[MAX_TEAMS]<MAX_PLAYERS_IN_TEAM>;

public OnGameModeInit()
{
Iter_Init(Team);

#if defined Team_OnGameModeInit
return Team_OnGameModeInit();
#else
return 1;
#endif
}
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif

#define OnGameModeInit Team_OnGameModeInit
#if defined Team_OnGameModeInit
forward Team_OnGameModeInit();
#endif



stock Team_SetPlayerTeam(playerid, teamid)
{
new current_team = GetPlayerTeam(playerid);
if (current_team != NO_TEAM) {
Iter_Remove(Team[current_team], playerid);
}

if (teamid != NO_TEAM) {
Iter_Add(Team[teamid], playerid);
}

return SetPlayerTeam(playerid, teamid);
}
#if defined _ALS_SetPlayerTeam
#undef SetPlayerTeam
#else
#define _ALS_SetPlayerTeam
#endif

#define SetPlayerTeam Team_SetPlayerTeam

Использовать так:

new scores[MAX_TEAMS];
for (new teamid; teamid < MAX_TEAMS; teamid++) {
foreach (new playerid : Team[teamid]) {
scores[teamid] += GetPlayerScore(playerid);
}
}

for (new teamid; teamid < MAX_TEAMS; teamid++) {
printf("Team %d: %d", teamid, scores[teamid]);
}
Этот скрипт суммирует счёт каждой команды и выводит его

$continue$
24.03.2017, 10:17
Нельзя создавать "контейнер" по типу: ключ | значение (str)?

DeimoS
24.03.2017, 11:12
И не хватает примера с трехмерными массивами (взято отсюда (http://forum.sa-mp.com/showthread.php?t=570937), ибо я ленивый):

new
Iterator:Iter3[5][8]<10>;
for (new i = 0; i != Iter_InternalSize(Iter3); ++i)
{
Iter_Init(Iter3[i]);
}
Iter_Add(Iter3[3][6], 7);
Лишней такая информация не будет

Daniel_Cortez
24.03.2017, 11:21
Нельзя создавать "контейнер" по типу: ключ | значение (str)?
Статья про итераторы, а не про ассоциативные массивы.

По теме: жду добавления примера с массивом итераторов, обновления ссылки на предложенный выше форк (необязательно, но желательно) и, по возможности, разъяснения ситуации с форками проекта - тогда можно будет смело перемещать тему в раздел с проверенными статьями.

VVWVV
24.03.2017, 12:05
Предложу свой форк standalone библиотеки: https://github.com/Open-GTO/foreach/releases
Отличается наличием итераторов для актёров и транспорта (в том числе Stream итераторов, которые выключены по умолчанию), поддержкой множественных скриптов (FS + GM), оптимизированным ALS (без использования CallLocalFunction) и отсутствием поддержки npcmodes (для более чистого кода).

P.S. Не увидел примера использования массивов итераторов, предложу свой: https://github.com/Open-GTO/foreach#custom-array-of-iterators
Массив итераторов с игроками в команде:

#define MAX_TEAMS 255
#define MAX_PLAYERS_IN_TEAM MAX_PLAYERS / MAX_TEAMS

new
Iterator:Team[MAX_TEAMS]<MAX_PLAYERS_IN_TEAM>;

public OnGameModeInit()
{
Iter_Init(Team);

#if defined Team_OnGameModeInit
return Team_OnGameModeInit();
#else
return 1;
#endif
}
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif

#define OnGameModeInit Team_OnGameModeInit
#if defined Team_OnGameModeInit
forward Team_OnGameModeInit();
#endif



stock Team_SetPlayerTeam(playerid, teamid)
{
new current_team = GetPlayerTeam(playerid);
if (current_team != NO_TEAM) {
Iter_Remove(Team[current_team], playerid);
}

if (teamid != NO_TEAM) {
Iter_Add(Team[teamid], playerid);
}

return SetPlayerTeam(playerid, teamid);
}
#if defined _ALS_SetPlayerTeam
#undef SetPlayerTeam
#else
#define _ALS_SetPlayerTeam
#endif

#define SetPlayerTeam Team_SetPlayerTeam

Использовать так:

new scores[MAX_TEAMS];
for (new teamid; teamid < MAX_TEAMS; teamid++) {
foreach (new playerid : Team[teamid]) {
scores[teamid] += GetPlayerScore(playerid);
}
}

for (new teamid; teamid < MAX_TEAMS; teamid++) {
printf("Team %d: %d", teamid, scores[teamid]);
}
Этот скрипт суммирует счёт каждой команды и выводит его

Спасибо за информацию.


И не хватает примера с трехмерными массивами (взято отсюда (http://forum.sa-mp.com/showthread.php?t=570937), ибо я ленивый):

new
Iterator:Iter3[5][8]<10>;
for (new i = 0; i != Iter_InternalSize(Iter3); ++i)
{
Iter_Init(Iter3[i]);
}
Iter_Add(Iter3[3][6], 7);
Лишней такая информация не будет

В автономной версии библиотеки данный способ не работает, ибо она может поддерживать только массив итераторов.


По теме: жду добавления примера с массивом итераторов, обновления ссылки на предложенный выше форк (необязательно, но желательно) и, по возможности, разъяснения ситуации с форками проекта - тогда можно будет смело перемещать тему в раздел с проверенными статьями.

Обновил.

Alpano
25.03.2017, 09:33
Огромное спасибо ziggi за автономную версию) Отказывался от форыча исключительно из-за YSI... +100 к карме)

ziggi
25.03.2017, 12:39
Огромное спасибо ziggi за автономную версию) Отказывался от форыча исключительно из-за YSI... +100 к карме)

Это не моя заслуга, я лишь немного обновил существующую автономную версию.

Daniel_Cortez
25.03.2017, 17:13
Отредактировал описание отличий foreach и y_iterate в конце темы, подправил грамматику. Полагаю, можно перемещать.

vovandolg
26.03.2017, 12:42
Огромное спасибо ziggi за автономную версию) Отказывался от форыча исключительно из-за YSI... +100 к карме)

А причем там YSI, можно и без него обходится foreach'у, или вышла какая то неотъемлемая обнова в данном инклуде?

Alpano
26.03.2017, 13:04
А причем там YSI, можно и без него обходится foreach'у, или вышла какая то неотъемлемая обнова в данном инклуде?
До того как встретил эту статью, не знал что есть форыч через YSI.

vovandolg
26.03.2017, 13:08
До того как встретил эту статью, не знал что есть форыч через YSI.

он один) просто когда есть в папке библиотеки YSI он их начинает под себя грести и компилируется с некоторыми, потом мы удивляемся как YSI инк подключился без подключения:rofl:

Daniel_Cortez
26.03.2017, 15:24
он один) просто когда есть в папке библиотеки YSI он их начинает под себя грести и компилируется с некоторыми, потом мы удивляемся как YSI инк подключился без подключения:rofl:
Если y_iterate более новой версии, чем foreach, то foreach сам подключит вместо себя y_iterate - отсюда и подключение всех остальных инклудов из YSI.
В форке ziggi эта фича удалена.

DeimoS
26.04.2017, 23:25
Пример №3
Создадим скрипт, суммирующий счёт каждой команды. В нём нам необходимо использовать массива итераторов.

#define MAX_TEAMS 255
#define MAX_PLAYERS_IN_TEAM MAX_PLAYERS / MAX_TEAMS

new
Iterator:Team[MAX_TEAMS]<MAX_PLAYERS_IN_TEAM>;

public OnGameModeInit()
{
Iter_Init(Team);

#if defined Team_OnGameModeInit
return Team_OnGameModeInit();
#else
return 1;
#endif
}
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif

#define OnGameModeInit Team_OnGameModeInit
#if defined Team_OnGameModeInit
forward Team_OnGameModeInit();
#endif

stock Team_SetPlayerTeam(playerid, teamid)
{
new current_team = GetPlayerTeam(playerid);
if (current_team != NO_TEAM) {
Iter_Remove(Team[current_team], playerid);
}

if (teamid != NO_TEAM) {
Iter_Add(Team[teamid], playerid);
}

return SetPlayerTeam(playerid, teamid);
}
#if defined _ALS_SetPlayerTeam
#undef SetPlayerTeam
#else
#define _ALS_SetPlayerTeam
#endif

#define SetPlayerTeam Team_SetPlayerTeam

Использовать так:

new scores[MAX_TEAMS];
for (new teamid; teamid < MAX_TEAMS; teamid++) {
foreach (new playerid : Team[teamid]) {
scores[teamid] += GetPlayerScore(playerid);
}
}

for (new teamid; teamid < MAX_TEAMS; teamid++) {
printf("Team %d: %d", teamid, scores[teamid]);
}

Мне кажется или этот код не совсем рабочий? По крайней мере в том виде, в котором он имеется сейчас.
Возможно, в первоисточнике, откуда взят этот пример, формула в MAX_PLAYERS_IN_TEAM не такая странная для макроса, что указывается в качестве размера итератора, но сейчас она немножко бредовая

Да и вообще смысла от этого макроса нет, ибо нормально итератор будет работать только в таком виде:

new
Iterator:Team[MAX_TEAMS]<MAX_PLAYERS>;

ziggi
26.04.2017, 23:49
Мне кажется или этот код не совсем рабочий? По крайней мере в том виде, в котором он имеется сейчас.
Возможно, в первоисточнике, откуда взят этот пример, формула в MAX_PLAYERS_IN_TEAM не такая странная для макроса, что указывается в качестве размера итератора, но сейчас она немножко бредовая

Да и вообще смысла от этого макроса нет, ибо нормально итератор будет работать только в таком виде:

new
Iterator:Team[MAX_TEAMS]<MAX_PLAYERS>;

Что не так то? Код ведь компилируется и прекрасно работает. А подсчёт MAX_PLAYERS_IN_TEAM сделан таким образом, чтобы на сервере одновременно могло быть MAX_TEAMS команд. Если захочется больше игроков в команде - то нужно уменьшить количество команд, вполне логично.

DeimoS
27.04.2017, 00:12
Что не так то? Код ведь компилируется и прекрасно работает. А подсчёт MAX_PLAYERS_IN_TEAM сделан таким образом, чтобы на сервере одновременно могло быть MAX_TEAMS команд. Если захочется больше игроков в команде - то нужно уменьшить количество команд, вполне логично.

Ну представим, что MAX_PLAYERS не изменялось и равно 1000. Следовательно, в макросе MAX_PLAYERS_IN_TEAM будет записано "1000/255", что равно, примерно, 4, хотя компилятор округлит до 3-х, скорее всего (разницы особой нет). Следовательно, размер итератора тоже будет равен трём:

new
Iterator:Team[MAX_TEAMS]<3>;
Что означает, что в него нельзя будет записать число больше двух (ибо алгоритм таков).
То бишь, в этих итераторах будут записаны исключительно игроки с 0 по 2-ой ID и никто более.

Iter_Add(Team[0], 4);//Уже не запишет
Не думаю, что изначально именно такая была задумка.

ziggi
27.04.2017, 01:01
Ну представим, что MAX_PLAYERS не изменялось и равно 1000. Следовательно, в макросе MAX_PLAYERS_IN_TEAM будет записано "1000/255", что равно, примерно, 4, хотя компилятор округлит до 3-х, скорее всего (разницы особой нет). Следовательно, размер итератора тоже будет равен трём:

new
Iterator:Team[MAX_TEAMS]<3>;
Что означает, что в него нельзя будет записать число больше двух (ибо алгоритм таков).
То бишь, в этих итераторах будут записаны исключительно игроки с 0 по 2-ой ID и никто более.

Iter_Add(Team[0], 4);//Уже не запишет
Не думаю, что изначально именно такая была задумка.

Нет, это означает, что в итератор будет записано 3 игрока.

Iter_Add(Team[0], 4); // запишет
Iter_Add(Team[0], 999); // запишет
Iter_Add(Team[0], cellmax); // запишет
Iter_Add(Team[0], 1); // не запишет


UPD
А нет, всё так, мой косяк. Что-то я попутал...

DeimoS
27.04.2017, 01:15
Нет, это означает, что в итератор будет записано 3 игрока.

Iter_Add(Team[0], 4); // запишет
Iter_Add(Team[0], 999); // запишет
Iter_Add(Team[0], cellmax); // запишет
Iter_Add(Team[0], 1); // не запишет


UPD
А нет, всё так, мой косяк. Что-то я попутал...

Сам когда-то погорел на этом, долго не понимая почему не записывает в итератор мои значения :) Пока наконец не переборол упрямство вперемешку с желанием разобраться самостоятельно и не нашёл статью на sa-mp.com об итераторах, которую выше кидал.

Если кто не понял почему так происходит, то вот объяснение:

Итерирование происходит так: сначала читается последний элемент, если он не равен размеру итератора, то значение в ячейке записывается. Затем происходит ещё одна итерация, теперь уже сравнивается размер и значение элемента массива с индексом записанного числа и так много раз, пока значение элемента не будет равно размеру итератора. Поэтому вы не можете указывать числа больше размера итератора или меньше нуля, т.к. это нарушит алгоритм и вызовет ошибки.

DeimoS
28.04.2017, 16:59
Совсем забыл изначальную цель, ради которой я заглядывал в тему пару дней назад :D

Я бы ещё отдельно акцентировал внимание на том, что итераторы станут хорошей заменой всех массивов, значение которых впоследствии проверяется в цикле (особенно массивов, значение которых бывает равно только нулю и единице и массивов MAX_PLAYERS). У итераторов в этом плане сразу несколько плюсов, при том, что и возможности массива не теряются: и возможность "пробежаться" по нужным данными без дополнительных проверок, и узнать количество данных без дополнительных переменных или подсчёта в цикле (Iter_Count), не говоря уже о возможности получения случайного члена этих данных (Iter_Random).
А то для многих интеграция foreach в SA-MP заканчивается чем-то подобным:

foreach(new i: Player)
{
if(IsPlayerAdmin)
//...
}
хотя разумнее создать итератор

DeimoS
08.05.2017, 05:11
Кстати говоря, вот это ограничение:


Итерирование происходит так: сначала читается последний элемент, если он не равен размеру итератора, то значение в ячейке записывается. Затем происходит ещё одна итерация, теперь уже сравнивается размер и значение элемента массива с индексом записанного числа и так много раз, пока значение элемента не будет равно размеру итератора. Поэтому вы не можете указывать числа больше размера итератора или меньше нуля, т.к. это нарушит алгоритм и вызовет ошибки.

можно обойти. Правда, в некоторых случаях, придётся потратить немного больше памяти. Суть вот в чём.

Нужно создать обыкновенный массив с нужным нам количеством ячеек и итератор с таким же количеством.
Итератор не будет хранить каких-либо значений, кроме занятых индексов в основном массиве.
Проще показать на примере кода:


new test_array[MAX_PLAYERS],// Наш массив, который будет хранить нужные нам значения (ограничено лишь cellmin/cellmax)
Iterator:TestIter<sizeof(test_array)>; // Итератор с тем же числом ячеек, который будет хранить ID занятых ячеек в основном массиве (меньше 0 или больше MAX_PLAYERS-1 не записать)


public OnGameModeInit()
{
new idx,
i,
rand;

for(i = 0; i < 30; i++)// В этом цикле мы заносим данные в первые 30 ячеек массива
{
idx = Iter_Free(TestIter);// Для наглядности показываю как можно легко найти свободную ячейку в массиве
test_array[idx] = INVALID_PLAYER_ID;// Собственно, сама запись значения
Iter_Add(TestIter, idx);// И указание того, что ячейка занята
}

for(i = 0; i < 10; i++)// Теперь "удалим" 10 рандомных значений из массива
{
rand = random(30);
test_array[rand] = 0;// Обнулили ячейку (хотя на самом деле это не обязательно делать. Важно лишь удалить ячейку из итератора. Я прописал обнуление, дабы в последнем цикле наглядно показать какие ячейки будут выбраны великим псевдорандомом)
Iter_Remove(TestIter, rand);// И удалили ячейку из итератора, указав, что она освободилась (ВАЖНО!)
}

print("\n\nforeach:");// Теперь выведем в консоль значения массива при помощи foreach И обычного цикла, дабы сравнить данные
foreach(new t: TestIter)
{
printf("%i) %i", t, test_array[t]);
}
print("\nfor:");
for(i = 0; i < 30; i++)
{
printf("%i) %i", i, test_array[i]);
}
print("\n\n");

return 1;
}


foreach:
0) 65535
1) 65535
2) 65535
4) 65535
5) 65535
7) 65535
8) 65535
9) 65535
10) 65535
11) 65535
12) 65535
13) 65535
14) 65535
16) 65535
17) 65535
20) 65535
22) 65535
23) 65535
25) 65535
26) 65535
28) 65535
29) 65535

for:
0) 65535
1) 65535
2) 65535
3) 0
4) 65535
5) 65535
6) 0
7) 65535
8) 65535
9) 65535
10) 65535
11) 65535
12) 65535
13) 65535
14) 65535
15) 0
16) 65535
17) 65535
18) 0
19) 0
20) 65535
21) 0
22) 65535
23) 65535
24) 0
25) 65535
26) 65535
27) 0
28) 65535
29) 65535

Как видно, все ячейки, которым "сбросили" значение, не были выведены в foreach, а выведены лишь ячейки с нашим значением



Так, собственно, можно реализовать систему, что дана в примере

#define MAX_TEAMS 255
#define MAX_PLAYERS_IN_TEAM MAX_PLAYERS / MAX_TEAMS

new players_in_team[MAX_TEAMS][MAX_PLAYERS_IN_TEAM],
Iterator:Team[sizeof(players_in_team)]<sizeof(players_in_team[])>;


public OnGameModeInit()
{
Iter_Init(Team);

#if defined Team_OnGameModeInit
return Team_OnGameModeInit();
#else
return 1;
#endif
}
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif

#define OnGameModeInit Team_OnGameModeInit
#if defined Team_OnGameModeInit
forward Team_OnGameModeInit();
#endif



stock Team_SetPlayerTeam(playerid, teamid)
{
new
current_team = GetPlayerTeam(playerid),
idx = ITER_NONE;
if(current_team != NO_TEAM)
{
foreach(new i: Team[current_team])
{
if(players_in_team[current_team][idx] == playerid)
{
idx = i;
break;
}
}
Iter_Remove(Team[current_team], idx);
}

if(teamid != NO_TEAM)
{
idx = Iter_Free(Team[current_team]);
if(idx == ITER_NONE)// Если в команде нет места - возвращаем 0.
return 0;
players_in_team[current_team][idx] = playerid;
Iter_Add(Team[teamid], idx);
}

return SetPlayerTeam(playerid, teamid), 1;// Иначе - 1
}
#if defined _ALS_SetPlayerTeam
#undef SetPlayerTeam
#else
#define _ALS_SetPlayerTeam
#endif

#define SetPlayerTeam Team_SetPlayerTeam


new scores[MAX_TEAMS];
for (new teamid; teamid < MAX_TEAMS; teamid++)
{
foreach(new idx : Team[teamid])
{
scores[teamid] += GetPlayerScore(players_in_team[teamid][idx]);
}
}

for(new teamid; teamid < MAX_TEAMS; teamid++)
{
printf("Team %d: %d", teamid, scores[teamid]);
}

Собственно и остальные преимущества foreach сохраняются: при помощи Iter_Count можно узнать количество значений, занесённых в основной массив, при помощи Iter_Contains можно узнать, занята ли определённая ячейка и т.п.

P.S. Писал в браузере и на работоспособность не проверял. Но общий алгоритм прописан

VVWVV
08.05.2017, 06:23
...

Костыль

Думаю, можно это сделать в виде нового инклюда-дополнения для foreach, чтобы не запоминать алгоритм.

DeimoS
08.05.2017, 07:58
Костыль

Костыль, который позволяет сэкономить и память, и процессорное время в ситуациях, похожих на пример из статьи :mamba:


Думаю, можно это сделать в виде нового инклюда-дополнения для foreach, чтобы не запоминать алгоритм.

Я думал об этом написать, но, в итоге, забыл :pardon:
Как по мне, можно этот алгоритм и в основной инклуд записать, добавив дополнительный тэг, через который всё будет работать. А для тех, кто боится лишний код тащить за собой из библиотек, можно сообразить макрос, что позволит включать/отключать этот алгоритм.

VVWVV
08.05.2017, 08:08
Я думал об этом написать, но, в итоге, забыл :pardon:
Как по мне, можно этот алгоритм и в основной инклуд записать, добавив дополнительный тэг, через который всё будет работать. А для тех, кто боится лишний код тащить за собой из библиотек, можно сообразить макрос, что позволит включать/отключать этот алгоритм.

Наверное, стоит добавить это в форк с автономной версией. Я, наверное, подготовлю PR и отправлю его к ziggi. Хотя, если у тебя есть время, ты можешь сам это сделать.

koberman
08.06.2017, 14:13
foreach(Player, a)
{
if(player_info[a][ADMIN] > 0)
{
Iter_Random(a)
C:\Users\User\Documents\GTA San Andreas User Files\SAMP\server\gamemodes\typicalrp.pwn(9698) : error 017: undefined symbol "a@YSII_Cg"
C:\Users\User\Documents\GTA San Andreas User Files\SAMP\server\gamemodes\typicalrp.pwn(9698) : error 017: undefined symbol "a@YSII_Ag"
:blush2:

VVWVV
08.06.2017, 15:34
foreach(Player, a)
{
if(player_info[a][ADMIN] > 0)
{
Iter_Random(a)
C:\Users\User\Documents\GTA San Andreas User Files\SAMP\server\gamemodes\typicalrp.pwn(9698) : error 017: undefined symbol "a@YSII_Cg"
C:\Users\User\Documents\GTA San Andreas User Files\SAMP\server\gamemodes\typicalrp.pwn(9698) : error 017: undefined symbol "a@YSII_Ag"
:blush2:

Создайте итератор:


new Iterator:aa<MAX_PLAYERS>;

DeimoS
21.11.2017, 05:47
Вообще мне кажется, что стоит сделать упор на том, как устроены функции библиотеки, дабы не обмануть себя.

В пример такого обмана можно привести итератор для игроков в зоне стрима. Казалось бы, добавили функцию добавления игрока в итератор при входе в зону стрима, удаления при выходе и при тестах получили приличный прирост к скорости, но есть одно "но": что функция добавления итератора, что функция удаления - обе содержат в себе цикл и некоторые другие вычисления, которые будут вызываться 2 раза при входе/выходе каждого игрока из/в зону стрима.
В итоге получается, что в момент вызова цикла у нас получился прирост к скорости, но взамен мы получили постоянную нагрузку, которая, при хорошем онлайне, может стать гораздо вреднее, нежели цикл с проверкой координат

seriu
22.11.2017, 01:00
Вообще мне кажется, что стоит сделать упор на том, как устроены функции библиотеки, дабы не обмануть себя.

В пример такого обмана можно привести итератор для игроков в зоне стрима. Казалось бы, добавили функцию добавления игрока в итератор при входе в зону стрима, удаления при выходе и при тестах получили приличный прирост к скорости, но есть одно "но": что функция добавления итератора, что функция удаления - обе содержат в себе цикл и некоторые другие вычисления, которые будут вызываться 2 раза при входе/выходе каждого игрока из/в зону стрима.
В итоге получается, что в момент вызова цикла у нас получился прирост к скорости, но в замен мы получили постоянную нагрузку, которая, при хорошем онлайне, может стать гораздо вреднее, нежели цикл с проверкой координат

эт еще ничего)
есть такой вариант :D

#if FOREACH_I_PlayerVehiclesStream || FOREACH_I_VehiclePlayersStream

/*
Iter_OnVehicleStreamIn
*/

public OnVehicleStreamIn(vehicleid, forplayerid)
{
#if FOREACH_I_PlayerVehiclesStream
Iter_Add(PlayerVehiclesStream[forplayerid], vehicleid);
#endif

#if FOREACH_I_VehiclePlayersStream
Iter_Add(VehiclePlayersStream[vehicleid], forplayerid);
#endif

#if defined Iter_OnVehicleStreamIn
return Iter_OnVehicleStreamIn(vehicleid, forplayerid);
#else
return 1;
#endif
}
#if defined _ALS_OnVehicleStreamIn
#undef OnVehicleStreamIn
#else
#define _ALS_OnVehicleStreamIn
#endif

#define OnVehicleStreamIn Iter_OnVehicleStreamIn
#if defined Iter_OnVehicleStreamIn
forward Iter_OnVehicleStreamIn(vehicleid, forplayerid);
#endif

/*
Iter_OnVehicleStreamOut
*/

public OnVehicleStreamOut(vehicleid, forplayerid)
{
#if FOREACH_I_PlayerVehiclesStream
Iter_Remove(PlayerVehiclesStream[forplayerid], vehicleid);
#endif

#if FOREACH_I_VehiclePlayersStream
Iter_Remove(VehiclePlayersStream[vehicleid], forplayerid);
#endif

#if defined Iter_OnVehicleStreamOut
return Iter_OnVehicleStreamOut(vehicleid, forplayerid);
#else
return 1;
#endif
}
#if defined _ALS_OnVehicleStreamOut
#undef OnVehicleStreamOut
#else
#define _ALS_OnVehicleStreamOut
#endif

#define OnVehicleStreamOut Iter_OnVehicleStreamOut
#if defined Iter_OnVehicleStreamOut
forward Iter_OnVehicleStreamOut(vehicleid, forplayerid);
#endif

#endif

ziggi
22.11.2017, 12:59
эт еще ничего)
есть такой вариант :D

#if FOREACH_I_PlayerVehiclesStream || FOREACH_I_VehiclePlayersStream

/*
Iter_OnVehicleStreamIn
*/

public OnVehicleStreamIn(vehicleid, forplayerid)
{
#if FOREACH_I_PlayerVehiclesStream
Iter_Add(PlayerVehiclesStream[forplayerid], vehicleid);
#endif

#if FOREACH_I_VehiclePlayersStream
Iter_Add(VehiclePlayersStream[vehicleid], forplayerid);
#endif

#if defined Iter_OnVehicleStreamIn
return Iter_OnVehicleStreamIn(vehicleid, forplayerid);
#else
return 1;
#endif
}
#if defined _ALS_OnVehicleStreamIn
#undef OnVehicleStreamIn
#else
#define _ALS_OnVehicleStreamIn
#endif

#define OnVehicleStreamIn Iter_OnVehicleStreamIn
#if defined Iter_OnVehicleStreamIn
forward Iter_OnVehicleStreamIn(vehicleid, forplayerid);
#endif

/*
Iter_OnVehicleStreamOut
*/

public OnVehicleStreamOut(vehicleid, forplayerid)
{
#if FOREACH_I_PlayerVehiclesStream
Iter_Remove(PlayerVehiclesStream[forplayerid], vehicleid);
#endif

#if FOREACH_I_VehiclePlayersStream
Iter_Remove(VehiclePlayersStream[vehicleid], forplayerid);
#endif

#if defined Iter_OnVehicleStreamOut
return Iter_OnVehicleStreamOut(vehicleid, forplayerid);
#else
return 1;
#endif
}
#if defined _ALS_OnVehicleStreamOut
#undef OnVehicleStreamOut
#else
#define _ALS_OnVehicleStreamOut
#endif

#define OnVehicleStreamOut Iter_OnVehicleStreamOut
#if defined Iter_OnVehicleStreamOut
forward Iter_OnVehicleStreamOut(vehicleid, forplayerid);
#endif

#endif


Только они выключены по умолчанию.

DeimoS
22.11.2017, 14:37
эт еще ничего)
есть такой вариант :D

#if FOREACH_I_PlayerVehiclesStream || FOREACH_I_VehiclePlayersStream

/*
Iter_OnVehicleStreamIn
*/

public OnVehicleStreamIn(vehicleid, forplayerid)
{
#if FOREACH_I_PlayerVehiclesStream
Iter_Add(PlayerVehiclesStream[forplayerid], vehicleid);
#endif

#if FOREACH_I_VehiclePlayersStream
Iter_Add(VehiclePlayersStream[vehicleid], forplayerid);
#endif

#if defined Iter_OnVehicleStreamIn
return Iter_OnVehicleStreamIn(vehicleid, forplayerid);
#else
return 1;
#endif
}
#if defined _ALS_OnVehicleStreamIn
#undef OnVehicleStreamIn
#else
#define _ALS_OnVehicleStreamIn
#endif

#define OnVehicleStreamIn Iter_OnVehicleStreamIn
#if defined Iter_OnVehicleStreamIn
forward Iter_OnVehicleStreamIn(vehicleid, forplayerid);
#endif

/*
Iter_OnVehicleStreamOut
*/

public OnVehicleStreamOut(vehicleid, forplayerid)
{
#if FOREACH_I_PlayerVehiclesStream
Iter_Remove(PlayerVehiclesStream[forplayerid], vehicleid);
#endif

#if FOREACH_I_VehiclePlayersStream
Iter_Remove(VehiclePlayersStream[vehicleid], forplayerid);
#endif

#if defined Iter_OnVehicleStreamOut
return Iter_OnVehicleStreamOut(vehicleid, forplayerid);
#else
return 1;
#endif
}
#if defined _ALS_OnVehicleStreamOut
#undef OnVehicleStreamOut
#else
#define _ALS_OnVehicleStreamOut
#endif

#define OnVehicleStreamOut Iter_OnVehicleStreamOut
#if defined Iter_OnVehicleStreamOut
forward Iter_OnVehicleStreamOut(vehicleid, forplayerid);
#endif

#endif


Я думаю, далеко не во всех случаях они будут бесполезны (Какой-нибудь античит, например, где нужно в радиусе проверку делать. Хотя тут нужно проводить замеры нагрузки, по хорошему, ибо может быть разная картина при маленьком и большом онлайне).
Вообще речь ведь о том, что нужно использовать всё сознательно (чувствую себя Капитаном Очевидность, хотя почему-то не все этой истины понимают). Тогда и проблем не будет

execution
21.02.2020, 18:31
И не хватает примера с трехмерными массивами (взято отсюда (http://forum.sa-mp.com/showthread.php?t=570937), ибо я ленивый):

new
Iterator:Iter3[5][8]<10>;
for (new i = 0; i != Iter_InternalSize(Iter3); ++i)
{
Iter_Init(Iter3[i]);
}
Iter_Add(Iter3[3][6], 7);
Лишней такая информация не будет

При создании трёхмерного итератора, компилятор (zeex) жалуется:
ошибка 001: ожидался токен ";", но найден "-identifier-"


const
MAX_FRACTION = 5,
MAX_DIVISION = 10;

new
Iterator:iDivisionPlayers[MAX_FRACTION][MAX_DIVISION]<MAX_PLAYERS>;

Пробовал форк от Ziggi - аналогично

Daniel_Cortez
22.02.2020, 14:02
При создании трёхмерного итератора, компилятор (zeex) жалуется:
ошибка 001: ожидался токен ";", но найден "-identifier-"


const
MAX_FRACTION = 5,
MAX_DIVISION = 10;

new
Iterator:iDivisionPlayers[MAX_FRACTION][MAX_DIVISION]<MAX_PLAYERS>;

Пробовал форк от Ziggi - аналогично
Насколько я понимаю, в foreach просто не предусмотрена возможность создания многомерных итераторов. В качестве обходного варианта можно объявить итератор примерно так:
new Iterator:iDivisionPlayers[MAX_FRACTION * MAX_DIVISION]<MAX_PLAYERS>;
и потом обращаться к нему как "iDivisionPlayers[fractionid * MAX_FRACTION + divisionid]".