Списки и перебор
list — это третий тип значений в языке вычислений, наравне с int и str. Список
строят тегом <list>, обходят через
<each> или <reduce>, а
обратно в значение превращают через <join>,
<at> или <length>.
Всё на этой странице держится на двух правилах:
- Список сам по себе не может покинуть
<compute>. Значение последовательности — это строка, поэтому голый список сначала нужно свернуть — через<join>(в строку),<reduce>(в одно значение) или<at>(один элемент). Именно поэтому вокруг<each>почти всегда стоит<join>или<reduce>. - Строка перебирается по одному символу.
<field name="Base"/>со значением"5120"проходится как5, 1, 2, 0, и каждая отдельная цифра автоматически превращается вintдля арифметики. Именно это и делает выразимыми контрольные суммы, которые идут по значению цифра за цифрой. Буквы перебираются так же:"abc"даётa, b, c.
Поскольку любой цикл идёт по конечной строке или списку, язык всегда завершается: неограниченных циклов нет.
Откуда берётся список
Источника три, и только три. Любой список на этой странице — один из них.
- Написан руками —
<list v="10,20,30"/>. - Строка, перебираемая по символам — везде, где ожидается список, строка встаёт на
его место как список своих символов. Вот это и удивляет: в конфиге нигде не написано
«раздели», слот
<over>просто принимает строку. - Результат
<each>— список на входе, список на выходе.
Все три в одном конфиге:
<tdc>
<env count="1" seed="src" local="en">
<sequence name="Code"><gen type="text" value="4816"/></sequence>
<sequence name="FromLiteral">
<compute><result><join sep="-"><in><list v="10,20,30"/></in></join></result></compute>
</sequence>
<sequence name="FromString">
<compute><result><join sep="-"><in>
<each><over><field name="Code"/></over><do><current/></do></each>
</in></join></result></compute>
</sequence>
<sequence name="FromEach">
<compute><result><join sep="-"><in>
<each><over><list v="1,2,3"/></over><do><multiply><current/><int v="10"/></multiply></do></each>
</in></join></result></compute>
</sequence>
</env>
<block><line><data>literal: ${{FromLiteral}} | string: ${{FromString}} | each: ${{FromEach}}</data></line></block>
</tdc>
literal: 10-20-30 | string: 4-8-1-6 | each: 10-20-30
Интересна средняя колонка: Code — это строка 4816, и <over> передал в <each>
четыре отдельных символа.
| Тег | Что делает |
|---|---|
<list> | литерал-список: <list v="2,4,10"/> или из детей |
<each> | применить тело к каждому элементу → новый список |
<reduce> | свернуть список в одно значение через накопитель |
<join> | список → строка (атрибут sep) |
<at> | один элемент по индексу (default, если вне диапазона) |
<length> | длина строки или списка |
Примеры вывода на этой странице — иллюстративные: точные значения зависят от seed и
версии ядра, но каждое производное значение вычисляется в точности из показанного рядом
входа.
<list> — литерал-список значений
Принимает v= со списком целых через запятую или дочерние выражения → отдаёт список. Буквы в v= отклоняются: <list v="a,b"/> падает с "a" is not an integer. Для списка слов соберите его из детей <str>.
<list> — это литерал списка. У него две формы:
- Короткая форма — целые числа через запятую в атрибуте
v:<list v="2,4,10"/>. Крайние пробелы обрезаются, пустые куски выбрасываются (" 2, ,4 "→2,4); нечисловой кусок — ошибка. - Форма с детьми —
<list>с вложенными тегами-выражениями собирает их значения:<list><int v="1"/><str v="a"/><field name="X"/></list>. Именно так список может держать строки, а не только числа.
Короткую форму применяют для фиксированной таблицы соответствий (веса контрольной суммы); форму с детьми — чтобы собрать несколько полей или вычисленных кусков перед свёрткой.
Из выражений → строка
Форма с детьми собирает два поля в список, а <join> его
отрисовывает (сам список результатом быть не может — только склеенная строка):
<sequence name="A"><gen type="number" value="1..9"/></sequence>
<sequence name="B"><gen type="number" value="1..9"/></sequence>
<sequence name="Pair">
<compute>
<result>
<join sep=",">
<list>
<field name="A"/>
<field name="B"/>
</list>
</join>
</result>
</compute>
</sequence>
Каждая строка — ${{Pair}}:
1,8 7,3 6,8 5,2 5,5
Как таблица весов
Короткая форма — фиксированная таблица, общая для каждой строки;
<at> достаёт из неё один элемент по позиции:
<sequence name="Idx"><gen type="number" value="0..4"/></sequence>
<sequence name="Weight">
<compute>
<result>
<at>
<in><list v="7,3,1,9,5"/></in>
<index><field name="Idx"/></index>
</at>
</result>
</compute>
</sequence>
Каждая строка — индекс ${{Idx}} -> вес ${{Weight}}:
индекс 1 -> вес 3 индекс 0 -> вес 7 индекс 0 -> вес 7 индекс 3 -> вес 9 индекс 1 -> вес 3
Список 7,3,1,9,5 одинаков для каждой строки; меняется только индекс.
<each> — перебор списка
- Aсписок, по которому идёт <over> — по элементу на колонку
- Bвыражение <do>, одно и то же на каждом шаге; нарисовано пунктиром, потому что это один тег, а не четыре
- Cсписок, который отдаёт <each>, в том же порядке
Принимает слоты <over> (список) и <do> (что сделать с одним элементом) → отдаёт список, а не строку. Поэтому вокруг почти всегда стоит <join> или <reduce>: результат, положенный прямо в строку, падает с cannot use a list where a string is expected.
<each> идёт по коллекции, применяет тело <do> к каждому элементу и собирает
результаты в новый список той же длины — это «map» языка вычислений.
- Он принимает две обёртки:
<over>(что перебирать) и<do>(что вычислять). Отсутствие обёртки — ошибка дерева (TDC187). - Внутри
<do>доступны<current/>(сам элемент) и<current_index/>(его позиция с нуля). - Коллекция — это строка (каждый элемент — односимвольная строка; одиночная цифра
автоматически превращается в
intдля арифметики) или список. - Результат — всегда список, поэтому перед выходом из
<compute>его сворачивают через<reduce>или отрисовывают через<join>.
У <each> нет атрибутов — всё задаётся через <over> и <do>.
Сдвиг каждой цифры
Прибавить единицу к каждой цифре и взять по модулю 10 (чтобы 9 переходило в 0), затем
склеить цифры обратно через <join>:
<sequence name="Pin"><gen type="number" value="1000..9999"/></sequence>
<sequence name="Shift">
<compute>
<result>
<join sep="">
<each>
<over><field name="Pin"/></over>
<do><mod><add><current/><int v="1"/></add><int v="10"/></mod></do>
</each>
</join>
</result>
</compute>
</sequence>
Каждая строка — ${{Pin}} -> ${{Shift}}:
3115 -> 4226 9917 -> 0028 5120 -> 6231 5815 -> 6926 9444 -> 0555
Pin перебирается символ за символом: каждая цифра — односимвольная строка, поэтому
<add> берёт её без <to_number>.
<mod> с 10 держит результат в одной цифре. <each> выдаёт список
сдвинутых цифр, а <join sep=""> склеивает их без разделителя.
Пометить каждый элемент индексом
Внутри <do> тег <current_index/> даёт позицию элемента. Собираем куски индекс:символ
и склеиваем их через пробел:
<sequence name="Base"><gen type="number" value="100..900"/></sequence>
<sequence name="Labeled">
<compute>
<result>
<join sep=" ">
<each>
<over><field name="Base"/></over>
<do><concat><current_index/><str v=":"/><current/></concat></do>
</each>
</join>
</result>
</compute>
</sequence>
Каждая строка — ${{Base}} -> ${{Labeled}}:
541 -> 0:5 1:4 2:1 436 -> 0:4 1:3 2:6 567 -> 0:5 1:6 2:7 214 -> 0:2 1:1 2:4 597 -> 0:5 1:9 2:7
Применяйте, когда нужно преобразовать каждый элемент — сдвинуть или замаскировать
каждую цифру, закодировать каждый символ через <encode> или собрать
список слагаемых, которые контрольная сумма потом сложит.
<reduce> — свёртка в одно значение
Принимает слоты <over>, <init> и <do> → отдаёт одно значение.
Четыре части, как копилка
Вы идёте вдоль полки с банкой в руках. На каждом шаге смотрите на предмет в руке и решаете, что положить в банку. Список кончился — в банке ответ.
| Тег | На языке банки | Что держит |
|---|---|---|
<init> | что в банке до начала | стартовое значение |
<over> | полка, вдоль которой идёте | список или строка |
<do> | что делаете на каждом шаге | выражение, считается по разу на элемент |
<acc> | что в банке прямо сейчас | то, что <do> вернул в прошлый раз |
<current> | предмет в руке | элемент на этом шаге |
<current_index> | какой это шаг, считая с нуля | число |
<over>, <init> и <do> — это слоты: три разные работы, поэтому три разных имени.
Порядок между ними в файле не важен, а пропустить любой нельзя — нужны все три.
По шагам
- Aсимволы, по которым идёт <over> — по одному на шаг, номер шага снизу
- Bчто кладёт в банку <init> до первого шага; пунктиром, потому что это стартовое значение, а не шаг
- Cбанка после каждого шага — именно это читает <acc/> на следующем
- Dбанка после последнего шага, её и отдаёт <reduce>
Сумма цифр 4816:
<tdc>
<env count="1" seed="r" local="en">
<sequence name="Code"><gen type="text" value="4816"/></sequence>
<sequence name="Sum">
<compute>
<result>
<reduce>
<over><field name="Code"/></over>
<init><int v="0"/></init>
<do><add><acc/><current/></add></do>
</reduce>
</result>
</compute>
</sequence>
</env>
<block><line><data>${{Code}} → ${{Sum}}</data></line></block>
</tdc>
4816 → 19
Четыре шага, по строке на каждый. acc до шага — это то, что вернул предыдущий шаг;
самый первый берётся из <init>.
| Шаг | <current_index/> | <current/> | <acc/> до | <do> считает | <acc/> после |
|---|---|---|---|---|---|
| 1 | 0 | 4 | 0 | 0 + 4 | 4 |
| 2 | 1 | 8 | 4 | 4 + 8 | 12 |
| 3 | 2 | 1 | 12 | 12 + 1 | 13 |
| 4 | 3 | 6 | 13 | 13 + 6 | 19 |
Банка после последнего шага — 19 — и есть то, что <reduce> возвращает.
<reduce> идёт по коллекции и сворачивает её в единственное значение: он задаёт
начальное значение накопителя, а затем пересчитывает его для каждого элемента. В отличие
от <each>, который возвращает список, <reduce> возвращает
одно значение (int или str), которое можно вернуть прямо из <compute>. Именно так
строят контрольные суммы — цифру карты по Луну, взвешенную цифру ID по mod-11, IBAN по
mod-97.
- Он принимает три обёртки:
<over>(коллекция),<init>(начальное значение накопителя) и<do>(тело для каждого элемента). Отсутствие обёртки — ошибка дерева (TDC187). - Внутри
<do>доступны<acc/>(что накоплено на данный момент),<current/>(элемент) и<current_index/>(его позиция с нуля). - Коллекция — это строка (обходится символ за символом; одиночная цифра автоматически преобразуется) или список.
- Накопитель не ограничен суммой — он может держать максимум, произведение и даже строку.
Это то, что вы кладёте обратно в
<acc/>на каждом шаге.
У <reduce> нет атрибутов — всё задаётся через <over>, <init> и <do>.
Сумма цифр
Самый частый случай: начать с 0 и прибавлять каждую цифру к накопителю:
<sequence name="Pin"><gen type="number" value="1000..9999"/></sequence>
<sequence name="DigitSum">
<compute>
<result>
<reduce>
<over><field name="Pin"/></over>
<init><int v="0"/></init>
<do><add><acc/><current/></add></do>
</reduce>
</result>
</compute>
</sequence>
Каждая строка — ${{Pin}} -> сумма ${{DigitSum}}:
3115 -> сумма 10 9917 -> сумма 26 5120 -> сумма 8 5815 -> сумма 19 9444 -> сумма 21
3 + 1 + 1 + 5 = 10, 9 + 9 + 1 + 7 = 26. Каждая цифра приходит как односимвольная строка
и автоматически превращается в число, так что <to_number> не нужен.
Взвешенная контрольная цифра
Флагманский случай и шаблон, которым завершаются многие номера счетов и национальные
идентификаторы — например, российский ИНН. У каждой позиции свой вес; вес по индексу
достаётся через <at>, умножается на цифру, всё суммируется,
затем сумма превращается в контрольную цифру двойным <mod> (% 11,
потом % 10) и приклеивается к базе через <concat>:
<sequence name="Base"><gen type="number" value="100000000..999999999"/></sequence>
<sequence name="Inn">
<compute>
<let name="check">
<mod><mod>
<reduce>
<over><field name="Base"/></over>
<init><int v="0"/></init>
<do><add><acc/><multiply><current/>
<at><in><list v="2,4,10,3,5,9,4,6,8"/></in><index><current_index/></index></at>
</multiply></add></do>
</reduce>
<int v="11"/></mod><int v="10"/></mod>
</let>
<result><concat><field name="Base"/><var name="check"/></concat></result>
</compute>
</sequence>
Каждая строка — ${{Base}} -> ${{Inn}}:
596215738 -> 5962157388 478374300 -> 4783743000 625770577 -> 6257705772 228098087 -> 2280980875 659123186 -> 6591231869
<reduce> накапливает взвешенную сумму: <current_index/> выбирает вес из 2,4,10,…,
<multiply> масштабирует цифру, а <add> сворачивает её в <acc/>. Сумма превращается в
контрольную цифру, и <concat> приписывает её к базе — получается корректный десятизначный
ИНН.
Накопитель — не только сумма
Накопитель держит наибольшую встреченную цифру; на каждом шаге
<choose> оставляет ту, что больше — цифру или накопитель:
<sequence name="Pin"><gen type="number" value="1000..9999"/></sequence>
<sequence name="MaxDigit">
<compute>
<result>
<reduce>
<over><field name="Pin"/></over>
<init><int v="0"/></init>
<do>
<choose>
<when><test><greater_than><current/><acc/></greater_than></test>
<then><current/></then></when>
<otherwise><acc/></otherwise>
</choose>
</do>
</reduce>
</result>
</compute>
</sequence>
Каждая строка — ${{Pin}} -> макс ${{MaxDigit}}:
3115 -> макс 5 9917 -> макс 9 5120 -> макс 5 5815 -> макс 8 9444 -> макс 9
Старт с 0 работает, потому что цифры неотрицательны. На каждом шаге он сравнивает
<current/> с <acc/> и оставляет большее — доказательство того, что <reduce> считает
не только суммы.
Применяйте, когда из списка нужно одно значение: контрольная сумма, итог, произведение,
бегущий максимум или минимум. Когда вместо этого нужен список результатов, берите
<each>.
<join> — список в строку
Принимает слот <in> (список) и sep= → отдаёт строку. Без sep= элементы склеиваются вплотную.
<join> превращает список в строку: он отрисовывает каждый элемент в текст и
соединяет их разделителем sep. Это главный способ выпустить список из <compute> —
голый список не может стать значением последовательности, но после <join> это обычная
строка. Он очень часто стоит сразу после <each>.
- Каждый элемент отрисовывается в текст: число становится десятичной записью, строка остаётся как есть.
- На входе должен быть список — одиночное число или строка — ошибка.
| Атрибут | Обязателен | По умолчанию | Что задаёт |
|---|---|---|---|
sep | нет | "" (пусто) | разделитель между элементами |
Собрать составное значение
Кладём три числа в <list> и склеиваем их точками в
major.minor.patch. Значения полей — строки, поэтому оборачиваем их в
<to_number>, чтобы список стал числовым:
<sequence name="A"><gen type="number" value="10..99"/></sequence>
<sequence name="B"><gen type="number" value="10..99"/></sequence>
<sequence name="C"><gen type="number" value="10..99"/></sequence>
<sequence name="Ver">
<compute>
<result>
<join sep=".">
<list>
<to_number><field name="A"/></to_number>
<to_number><field name="B"/></to_number>
<to_number><field name="C"/></to_number>
</list>
</join>
</result>
</compute>
</sequence>
Каждая строка — ${{Ver}}:
10.83.55 73.32.50 67.82.98 59.29.97 56.58.26
Разредить цифры (<join> после <each>)
Частая пара: <each> превращает строку в список её символов, а
<join> собирает их обратно — уже с разделителем:
<sequence name="Card"><gen type="number" value="10000000..99999999"/></sequence>
<sequence name="Spaced">
<compute>
<result>
<join sep=" ">
<each>
<over><field name="Card"/></over>
<do><current/></do>
</each>
</join>
</result>
</compute>
</sequence>
Каждая строка — ${{Card}} -> ${{Spaced}}:
76899955 -> 7 6 8 9 9 9 5 5 96915004 -> 9 6 9 1 5 0 0 4 71643027 -> 7 1 6 4 3 0 2 7 27885493 -> 2 7 8 8 5 4 9 3 46564820 -> 4 6 5 6 4 8 2 0
Применяйте, когда выпускаете список из <compute> (почти всегда после <each>) или
собираете составное значение — версию, путь, координаты. Для фиксированной группировки
одной строки справа налево проще <group>; <join> соединяет элементы
списка.
<at> — доступ по индексу
Принимает слоты <in> (список) и <index> (число, счёт с нуля) → отдаёт один элемент. Индекс за границей — ошибка, если не задано значение по умолчанию.
<at> достаёт из списка один элемент по позиции с нуля. Он идеален для таблиц
соответствий: индекс → готовое значение (вес контрольной суммы, название региона,
налоговая ставка).
- Он принимает две обёртки:
<in>(какой список) и<index>(какая позиция). Отсутствие обёртки — ошибка дерева (TDC187). - Индекс приводится к
int: односимвольная строка-цифра (поле со значением"3") работает как есть; многозначную строку сначала оберните в<to_number>. - В
<in>должен быть список (<list>или результат<each>). - Вне диапазона: если задан атрибут
default, возвращается он; безdefault— ошибка. Так у таблицы появляется запасное значение.
| Атрибут | Обязателен | По умолчанию | Что задаёт |
|---|---|---|---|
default | нет | — | целое, возвращаемое, когда индекс вне диапазона |
Таблица подстановки
Список названий и индекс из поля. Idx — одна цифра 0..4, поэтому
<to_number> не нужен:
<sequence name="Idx"><gen type="number" value="0..4"/></sequence>
<sequence name="City">
<compute>
<result>
<at>
<in><list>
<str v="Москва"/>
<str v="Санкт-Петербург"/>
<str v="Новосибирск"/>
<str v="Екатеринбург"/>
<str v="Казань"/>
</list></in>
<index><field name="Idx"/></index>
</at>
</result>
</compute>
</sequence>
Каждая строка — ${{Idx}} -> ${{City}}:
1 -> Санкт-Петербург 0 -> Москва 0 -> Москва 3 -> Екатеринбург 1 -> Санкт-Петербург
Список строк собран формой с детьми тега <list>;
<at> берёт элемент под номером Idx — таблица подстановки.
Значение по умолчанию при выходе за границы
В списке три цены (позиции 0..2), но индекс приходит из 0..6. Для 3..6 элемента нет,
поэтому срабатывает default="0":
<sequence name="Idx"><gen type="number" value="0..6"/></sequence>
<sequence name="Price">
<compute>
<result>
<at default="0">
<in><list v="100,200,300"/></in>
<index><field name="Idx"/></index>
</at>
</result>
</compute>
</sequence>
Каждая строка — индекс ${{Idx}} -> ${{Price}}:
индекс 2 -> 300 индекс 0 -> 100 индекс 0 -> 100 индекс 4 -> 0 индекс 1 -> 200 индекс 1 -> 200
Индексы 0..2 попадают в цену; индекс 4 — за концом, поэтому вместо ошибки возвращается
default, то есть 0. Без default эта строка упала бы во время генерации.
Применяйте, когда нужна таблица соответствий (вес контрольной суммы по позиции, код,
ставка по разряду) или безопасный доступ по индексу, где default заменяет «нет такого
элемента» на разумное значение.
<length> — длина строки или списка
Принимает одну строку или список → отдаёт число: символы в строке, элементы в списке. Считает символы, а не байты — 👍ab это 3.
<length> возвращает длину — число символов, если внутри строка, или число элементов,
если внутри список. Результат всегда int, поэтому с ним можно считать: сравнивать, делить,
дополнять.
<length>СТРОКА</length>— число символов (кодовых точек) в строке.<length>СПИСОК</length>— число элементов в списке (например, результат<each>).- На входе должна быть строка или список — число — ошибка.
У <length> нет атрибутов — он измеряет своего единственного ребёнка.
Символы в строке
Значение поля — строка, и <length> считает её символы:
<sequence name="City"><gen type="text" value="Пермь,Москва,Смоленск,Ярославль,Новосибирск"/></sequence>
<sequence name="Len">
<compute>
<result>
<length><field name="City"/></length>
</result>
</compute>
</sequence>
Каждая строка — ${{City}} -> ${{Len}} букв:
Москва -> 6 букв Пермь -> 5 букв Смоленск -> 8 букв Ярославль -> 9 букв Новосибирск -> 11 букв
Каждый символ — одна кодовая точка, поэтому «Новосибирск» даёт 11.
Длина как знаменатель
Реалистичный случай: разделить сумму цифр из <reduce>
на число цифр — знаменатель даёт <length>. <divide> — целочисленное
деление (с округлением вниз):
<sequence name="Pin"><gen type="number" value="1000..9999"/></sequence>
<sequence name="Avg">
<compute>
<result>
<divide>
<reduce>
<over><field name="Pin"/></over>
<init><int v="0"/></init>
<do><add><acc/><current/></add></do>
</reduce>
<length><field name="Pin"/></length>
</divide>
</result>
</compute>
</sequence>
Каждая строка — ${{Pin}} -> среднее ${{Avg}}:
3115 -> среднее 2 9917 -> среднее 6 5120 -> среднее 2 5815 -> среднее 4 9444 -> среднее 5
Для 3115: сумма 10, длина 4, 10 / 4 = 2 (дробная часть отбрасывается).
Элементы в списке
<length> измеряет и списки. <each> превращает строку в список
её символов, а <length> считает, сколько их получилось:
<sequence name="Num"><gen type="number" value="[1..9],[10..99],[1000..99999]"/></sequence>
<sequence name="Count">
<compute>
<result>
<length>
<each>
<over><field name="Num"/></over>
<do><current/></do>
</each>
</length>
</result>
</compute>
</sequence>
Каждая строка — ${{Num}} -> длина ${{Count}}:
79 -> длина 2 67 -> длина 2 30181 -> длина 5 8 -> длина 1 90 -> длина 2
<each> возвращает список символов, а <length> считает его элементы — здесь это равно
числу цифр во входе.
Применяйте, когда форма алгоритма зависит от размера входа: веса, зависящие от длины,
среднее или отношение, проверка, которая сравнивает длину с целью через
<choose>.
Смотрите также
- Арифметика — целочисленные операции, которыми сворачивают и индексируют.
- Строки и форматирование —
<concat>,<group>и остальное. - Условия —
<choose>и предикаты, использованные выше. - Обзор compute —
<reduce>внутри полной контрольной суммы. - Справочник функций compute — полный алфавитный каталог.