#1.6 Циклы в Loginom

В предыдущих сериях мы освоились с основными механиками простых сценариев. Пора двигаться в более сложные темы. Циклы — важная часть разработки сценариев. С их помощью можно автоматизировать выполнение однотипных операций (вы ведь не хотите создавать 100 блоков импорта для 100 файлов для загрузки? :)). Тема циклов интересна еще и тем, что для ее разбора мы затронем более продвинутые механики проектирования сценариев.

Очевидно, циклы позволят нам многократно выполнять последовательность узлов, с параметризированием его настроек на каждой итерации цикла. Для этого у нас есть отдельный блок, который называется Цикл.

Цикл может выполняться в 3-х режимах:

for i=x to y … next — выполнение цикла заданное кол-во раз с автоитерированием счетчика выполнений.

do while i<=x … loop — выполнение цикла до тех пор, пока выполняется условие значения переменной. Используется когда выполнение цикла нужно прервать не по открутке определенного кол-ва итераций, а по выполнению условия. Потенциально может создать бесконечное выполнение (когда прерывающее условие никогда не выполняется), так что с этим типом цикла нужно быть внимательным.

for each i in <set of values> … next — выполнение цикла с подстановкой значений из списка (поля).

Из интересных моментов: итерации цикла первого и третьего типа могут выполняться параллельно, при соответствующих настройках в узле Цикл. Также, в текущей версии Loginom для цикла типа for i=x to y … next нельзя задать через переменную кол-во итераций цикла. Т.е. итераций будет столько, сколько вы константно выставите в настройках узла. Это значит, что в работе в 99% случаев вы будете использовать циклы do while i<=x … loop и for each i in <set of values> … next. Также, с их помощью можно решить кейс, когда нам нужно сделать динамически определяемое количество итераций цикла типа or i=x to y … next. Этим и займемся.

Do while в Loginom

Не смотря на наличие узла Цикл в библиотеке Loginom как отдельного элемента, сам по себе он ничего не делает. Чтобы цикл работал, его нужно ассоциировать с узлом вида Подмодель (или некоторыми другими). Подмодель — это как бы папка, в которую складываются другие узлы. На базе подмоделей можно создавать собственные компоненты, оптимизировать проектирование сценария. Ну и конечно же, создавать последовательности операций для выполнения в цикле.

Давайте поставим себе первую цель — сделать цикл, который будет генерировать нам таблицу с одним полем, в котором будет содержаться последовательность значений от x до y. Которую потом можно будет скормить другому циклу, и сымитировать цикл for i=x to y … next с динамически задаваемым количеством итераций. Чтобы добиться такого результата, мы спроектируем цикл типа do while … loop.

Т.е. наша цель — сделать генерацию таблицы с порядковыми номерами от x до y, с шагом z.

Проектирование цикла всегда начинается с добавления в сценарий узла Подмодель. Наша задача — внутри подмодели создать сценарий одной итерации цикла.

Зайдем в настройки подмодели.

На уровне узла в подмодели настраивается только набор входных и выходных портов. Нам ведь нужно, чтобы данные попали внутрь модуля, а потом вышли из него?

При создании портов нам нужно иметь представление, какие данные будут заводиться в подмодель, а какие выходить. В данном случае план такой — на вход будет подаваться набор переменных с настройками цикла. На выходе будет одна таблица. Также на выходе нам потребуется порт переменных, для целей цикла do while.

Для циклов do while … loop в подмодели всегда должны присутствовать входные и выходные порты переменных, для целей управления количеством повторений цикла. Для циклов for each … next это не обязательное условие

Если вы хотите заводить/выводить из под модели несколько таблиц, то для каждой нужно создавать отдельный входной/выходной порт. В случае с переменными этого можно не делать, т.к. в одном потоке может передаваться несколько переменных. Однако, иногда для улучшения удобства работы с подмоделью, переменные тоже стоит разнести на разные порты.

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

Теперь, у нашей подмодели появились порты. Назовем ее «Генератор итераций».

Откроем настройки входного порта переменных, и создадим вот такой набор переменных. Все переменные создаем с типом «Целые».

Каково назначение этих переменных?

  • vLoopStatus — переменная, которая будет управлять моментом остановки цикла;
  • vStart — первое число в генерируемой последовательности;
  • vStop — максимальное число в генерируемой последовательности;
  • vStep — шаг в нумерации внутри последовательности;

На выходном порте переменных нам нужно создать в обязательном порядке переменные, которые: а) определяют завершение цикла; б) должны накапливать прогресс по итерациям цикла.

В нашем случае, это будут переменные vLoopStatus_New, и vStart_New. Лучший способ именования подобных переменных: брать название со входного порта, и добавлять к ним суффикс типа _New.

Переменные, помещенные в выходные порты, могут использоваться для:

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

Соответственно, в нашем случае, vLoopStatus_New будет сигналить, что цикл пора завершать. А vStart_New будет содержать значение vStart+vStep, и в конце подмодели передавать его обратно.

Войдем в подмодель.

Внутри все выглядит как и в обычном сценарии. Только по бокам видны входные и выходные порты самой подмодели.

Теперь, нам нужно спроектировать сценарий итерации цикла. Т.к. мы планируем цикл do while, то сценарий должен содержать 2 ветки:

  1. Итерирование управляющих переменных, чтобы цикл не выполнялся бесконечно.
  2. Собственно, сам сценарий генерации/обработки данных.

Начнем с итерирования переменных. Добавим узел Калькулятор (переменные), и заведем ему на вход переменные из входного порта подмодели.

Вычислим в калькуляторе 2 новых переменных: vStart_New=vStart+vStep.

А также, переменную vLoopStatus_New=if(vStart_New>vStop,1,0)

Обратите внимание, что мы можем ссылаться в формуле vLoopStatus_New на свежесозданную переменную vStart_New, потому что vStart_New в списке рассчитываемых переменных идет выше чем vLoopStatus_New.

Мы специально показываем именно такой способ модификации управляющей переменной vLoopStatus_New, потому что в циклах do while … loop может потребоваться более сложная проверка условий продолжения цикла, нежели простое значение какой-то одной переменной. И такую проверку можно проводить в калькуляторе переменных, записывая ее итог как флаговое значение в управляющую переменную.

Протянем связь от выходного порта калькулятора к выходному порту переменных подмодели.

Зайдем в настройки выходного порта переменных подмодели. Переключимся в режим Связи. Вот что мы увидим:

На выход подаются все переменные калькулятора. Но нам из них нужны только те, которые мы создавали заранее, т.е. с _New на конце.. Поэтому удалим лишние выходные переменные:

Отлично! Мы написали логику для итерирования цикла. Но было бы неплохо, чтобы он еще выполнял некую полезную работу 🙂 Изначально мы хотели, чтобы у нас создавалась таблица с порядковыми номерами от x до y с шагом z. Так давайте дополним наш сценарий, чтобы это работало.

Добавим в подмодель узел Переменные в таблицу. На вход ей подадим переменные их входного порта подмодели.

Зайдем в настройки входного порта на узле Переменные в таблицу. Из всех входящих переменных оставим только переменную vStart — ведь именно ее значение мы будем сохранять в таблицу. А также будем итерировать ее на шаг vStep при каждом выполнении цикла с помощью vStart_New.

В настройках самого модуля убедимся, что выбрана опция записи переменных в столбцы.

Соединим выходной порт узла Переменные в таблицу с выходным портом подмодели.

Таким образом, мы закончили проектирование итерации цикла. Выйдем из подмодели в основной сценарий.

Выполним узел подмодели. Как результат, на выходном порте с таблицей мы увидим таблица с полем vStart, и одной строкой со значением 1. Это соответствует одной итерации выполнения цикла.

Но как нам выполнить эту подмодель заданное количество раз? Добавим узел Цикл, и зайдем в его настройки.

Сам по себе узел Цикл ничего не умеет. Чтобы он начал работать, нужно задать в его настройках ссылку на одну из подмоделей сценария, или внешних компонентов (т.е. подмоделей из других пакетов/сценариев. Как это делать, разберем в другом занятии).

Далее, нам предложат выбрать, в каком режиме этот цикл будет работать. Очевидно, что нас интересует второй вариант: Цикл с постусловием.

Первое, что нам нужно будет выбрать — какую переменная отвечает за остановку цикла. Здесь мы увидим список переменных с выходного узла подмодели.

Нас интересует vLoopStatus_New, потому что именно она содержит флаг, который сигнализирует, что циклу пора останавливаться. Здесь мы задаем условие завершения цикла. А именно, vLoopStatus_New=1.

Теперь вы понимаете, почему мы делали расчет управляющей переменной во флаговом варианте. В настройках узла у нас нет возможности задать динамическое условие окончания цикла — только равенство константе. А в do while циклах может потребоваться не только динамическое значение, определяющее конец цикла. Но и проверка сразу нескольких условий. Такую проверку вы можете выполнять в узле Калькулятор переменных внутри цикла. И сохранять ее итог во флаговом виде в управляющую переменную.

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

В нашем случае, из переменной vStart_New нужно передавать значение в vStart, чтобы в каждой новой итерации значение, сохраняемое в таблицу становилось все больше. Ну и чтобы наш цикл однажды закончился :). Передавать значение из vLoopStatus_New в vLoopStatus не имеет смысла, т.е. расчет этой переменной зависим от итерируемой переменной vStart.

Выполните Цикл. Посмотрите на его выходной табличный порт. Там должна быть таблица с полем vStart и значениями от 1 до 10.

Таким образом, в табличных выходах цикла аккумулируется итоговая таблица из всех выполненных итераций цикла. Цикл является ссылочным узлом. Понять это можно по соответствующему значку. Также на верхней панели можно отбразить связи ссылочных узлов с исходными узлами.

Как вы могли заметить, узел Цикл копирует входные и выходные порты исходного узла. При этом содержимое этих портов может быть настроено независимо от исходного узла. Попробуйте изменить переменную vStep на входном порте цикла, задав ей значение, например, 5. Результат вычисления цикла будет уже другой.

Таким образом, можно вызвать одну и ту же подмодель в 2-х разных циклах с разными настройками, и получить разные результаты. Попробуйте.

Цикл for each в Loginom

К счастью, этот вид цикла работает намного проще, чем do while. Импортируйте файл продаж из занятия 1.3 (или скачайте его заново тут). Сейчас мы покрутим данные из этой таблицы в цикле for each, используя для этого последовательность значений, подготовленную циклом do while.

Импортируйте таблицу продаж, и добавьте новый узел Подмодель.

В общем случае, для цикла for each в подмодели потребуется 2 табличных порта: один для списка итераций, другой для непосредственно данных. И один табличный выходной порт.

Кстати, названия портов выводятся при наведении на них курсора мышки, что ОЧЕНЬ удобно для проектирования сложных сценариев. Заводим в подмодель в порт для итераций результат какого-нибудь цикла do while, а в порт для данных — таблицу продаж.

Дальше делаем по стандартному плану — идем в подмодель и проектируем одну итерацию цикла. Т.к. мы хотим не просто прокрутить цикл по количеству строк из таблицы итераций, но и использовать сами значения таблицы внутри цикла, их нужно будет конвертировать в переменные. Это делается с помощью узла Таблицы в переменные, который нам надо разместить внутри нашей новой подмодели. Заводим в узел таблицу из входа Итерации.

В настройках узла перемещаем поле vStart в правую часть, дважды щелкаем по нему ПКМ и задаем агрегацию «Первый». Во время работы цикла в подмодель будет поступать всегда одна строка итерационной таблицы, поэтому мы просто выбираем тот вариант агрегации, который подошел бы независимо от типа данных.

Далее, добавим узел Калькулятор (переменные), где создадим переменную vSearch с форматом Текст, в которой будет содержаться значение vStart.

Добавляем узел Калькулятор (переменные)
Создаем переменную vSearch, как текстовое представление vStart

Добавим узел Фильтр строк, в который заведем переменные из калькулятора, и таблицу из входного порта подмодели, который передает данные с таблицы продаж. Для отображения порта входных переменных на фильтре, щелкните по нему ПКМ, и выберите пункт «Показать блок управляющих переменных».

В настройках фильтра задайте условие для поля Клиент — содержит, и переключитесь на переменную vSearch. Заметьте, т.к. Клиент — это текстовое поле, то в условие к нему могут передаться только текстовые переменные.

Протяните выход из фильтра в выходному порту подмодели. Посмотрите на выходном порте фильтра, что у вас действительно отфильтровались клиенты, которые содержат в названии строку из переменной.

Отлично. Одна итерация цикла готова. По отработанной схеме, добавляем узел цикл, и настраиваем его на выполнение подмодели.

В этот раз выберем вариант цикла Групповая обработка, вид обработки — разбиение по уникальным значениям полей, входной порт — Итерации.

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

Теперь мы можем в настройках цикла выбрать поле, которое будет источником итераций. А в нашем случае, это единственное поле с названием vStart, которое поступает в таблице на входной порт Итерации.

Выполните сценарий. Как понять, что цикл for each сработал? Скорее всего, он будет выполняться чуть дольше чем остальные узлы. Ну и количество строк в итоговой таблице должно отличаться от количества строк в исходной таблице Продажи.

Кстати, в сценарии можно убрать передачу данных на порты подмодели foe each, оставив ее только для узла цикла.

Правда чтобы цикл выполнялся, нужно нужно будет щелкнуть по нему ПКМ, и выбрать опцию «Обновить конфигурацию» узла, т.к. раньше эти настройки заимствовались из подмодели, а теперь мы обрубили в ней провода. Таким образом, цикл будет полагаться на настройки из собственных портов.

При выполнении сценария, исходная подмодель for each Будет светиться красным, потому что ей на вход не подаются обязательные таблицы. Но циклу это не важно, потому что на него все подается, и он ссылается на узлы в подмодели. И таким образом все работает.

Возникает вопрос: а обязательно эти подмодели должны висеть в сценарии и сигналить красным цветом? Ответ — нет, т.к. их можно вынести в другой сценарий или даже в другой пакет, и в рабочем сценарии просто ссылаться на них. Но это мы разберем в следующем занятии.

Добавить комментарий

Ваш адрес email не будет опубликован.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.