Перейти к основному содержимому

Последовательности

Тег <sequence> объявляет именованный столбец значений длиной в count карточек. Думайте о нём как о готовом столбце, который существует ещё до начала рендера: генератор заполняет все строки, а на каждой выходной строке вы достаёте значение по имени — либо через интерполяцию ${{Name}}, либо внутри условия if.

Это логическая модель, и всё на этой странице следует из неё. Лежит ли столбец в памяти на самом деле — отдельный вопрос, на который движок отвечает за вас: движок по умолчанию считает каждое значение по номеру строки, пока строка проходит мимо, и столбец не держит вовсе, а mode="memory" и объектный API массив действительно строят. Значения в обоих случаях одни и те же — см. Большие объёмы.

Последовательности живут в <env>, рядом с count и seed. Разметка строки (в <block>) сама значений не генерирует — она только читает столбцы, которые ваши последовательности уже произвели.

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

Шесть строк настоящего прогона. Каждая последовательность заполняет свою колонку, а строка — это то, что получается при чтении поперёк них.
  • Aодна последовательность — одна колонка, заполняется сама по себе
  • Bвторая последовательность, ничего не знающая о первой
  • Cтретья
  • Dодна строка: та же позиция, взятая из каждой колонки и собранная в строку вывода

Атрибуты

АтрибутОбязательныйЧто делает
nameдаИмя последовательности — по нему обращаются, например ${{City}}
parentнетОграничивает последовательность подмножеством строк. Синтаксис: Parent.Value
commentнетСвободный комментарий; движком игнорируется

Единственный обязательный вложенный тег — один или несколько <gen>, которые и производят сами значения.

Простая последовательность

Используйте её, когда нужен один столбец, к которому можно обращаться по имени из любого места разметки. Объявите его один раз в <env> и читайте через ${{City}}:

<tdc>
<env count="5" seed="demo">
<sequence name="City">
<gen type="text" value="Moscow,Berlin,Paris"/>
</sequence>
</env>
<block>
<line><data>${{City}}</data></line>
</block>
</tdc>
./run city.tdc (count=5, seed=demo)
Berlin
Moscow
Paris
Berlin
Paris

По умолчанию значения выбираются из списка случайно, а seed фиксирует, какие именно вам достанутся. Нужен строгий порядок списка? Добавьте order="sequential" — см. Маски и регистр.

«Простая» последовательность определяется тем, что у неё ровно один безымянный <gen>. Доступ — ${{ИмяПоследовательности}}.

С процентами

Та же форма из одного столбца покрывает и взвешенный выбор. Дайте генератору text список percent, и значения выйдут в точных пропорциях:

<sequence name="Gender">
<gen type="text" value="Мужчина,Женщина" percent="60,40"/>
</sequence>

На count=100 это даёт ровно 60 Мужчина и 40 Женщинаpercent задаёт точное разбиение на весь прогон, а не вероятность на каждой строке. О том, как percent соблюдается, см. Детерминизм и пропорции.

./run gender.tdc (count=100)
Мужчина  60
Женщина  40

Составная по значению

Когда нужна — когда одно значение собирается из нескольких: полное имя из имени, пробела и фамилии. Оставь <gen> без имён и поставь между ними текст:

<sequence name="FullName">
<gen type="template" value="person.male.firstName"/>
<data> </data>
<gen type="template" value="person.lastName"/>
</sequence>
./run people.tdc
Robert Williams
James Johnson
John Smith

${{FullName}} — одно значение, поэтому оно попадает в карточку, в колонку CSV или Parquet как есть: склеивать на выводе нечего.

Именованные и безымянные <gen> уживаются в одном теле. Безымянные собирают собственное значение последовательности, именованный остаётся полем рядом — он доступен как ${{Name.Field}} и в склейку не входит.

Составная последовательность

Используйте её, когда у одной «сущности» есть несколько связанных полей — имя, фамилия, возраст, — которые логически идут вместе. Сгруппируйте их в одну последовательность вместо трёх отдельных и дайте каждому <gen> атрибут name:

<sequence name="Person">
<gen name="FirstName" type="template" value="person.male.firstName"/>
<gen name="LastName" type="template" value="person.male.lastName"/>
<gen name="Age" type="number" value="18..60"/>
</sequence>

Обращайтесь к полям через точку:

<line><data>${{Person.FirstName}} ${{Person.LastName}}, возраст ${{Person.Age}}</data></line>
./run people.tdc
Демьян Кузьмин, возраст 39
Демьян Андреев, возраст 35
Эдуард Николаев, возраст 26

Константное поле

<data> с атрибутом name — это константное поле, и в отличие от генератора с одним значением оно не тратит розыгрыш:

<sequence name="Row">
<gen name="id" type="increment" value="1"/>
<data name="source">import-2026</data>
</sequence>

${{Row.source}} во всех записях равно import-2026.

Почему не <gen type="text" value="import-2026"/>

Он даёт то же значение и всё равно тратит по розыгрышу на строку, поэтому если вставить его в готовый конфиг, сдвинутся все колонки, объявленные после. Именованный <data> не тратит ничего — и это единственный способ добавить константу в конфиг, которым уже кто-то пользуется.

Правила составных

  • <gen> с name — это поле; без имени — часть собственного значения последовательности (см. составную по значению выше). Тело, где все <gen> именованы, — составная по полям, и своего значения у неё нет.
  • Имена полей должны быть уникальными внутри последовательности (ошибка TDC111 на дубликате).
  • Все поля делят один и тот же фильтр parent. Если родитель задан, каждое поле заполняется только на подходящих строках, а на остальных остаётся пустым.
  • Обращение к составной последовательности без указания поля${{Person}} — остаётся в тексте вывода как есть. Этот нетронутый плейсхолдер — подсказка, что вы имели в виду ${{Person.FieldName}}.

Поля в условиях if

Точечный доступ работает и внутри условий, не только в ${{...}}:

<line if="Person.Age >= 18"><data>взрослый</data></line>
<line if="Person.Age < 18"><data>несовершеннолетний</data></line>

Условная последовательность

Используйте её, когда значение столбца зависит от другого столбца. Перечислите несколько веток <gen if="…">; побеждает первая ветка, чьё условие истинно. Безымянный <gen> (без if) работает как «иначе»:

<sequence name="Age">
<gen type="number" value="0..90"/>
</sequence>

<sequence name="AgeGroup">
<gen if="Age < 18" type="text" value="Ребёнок"/>
<gen if="Age < 65" type="text" value="Взрослый"/>
<gen type="text" value="Пенсионер"/>
</sequence>

Поскольку ветки проверяются сверху вниз, Age < 65 видит только те строки, которые уже не прошли Age < 18, — то есть фактически означает «18–64». Доступ — по имени последовательности, ${{AgeGroup}}:

./run agegroup.tdc
14  Ребёнок
42  Взрослый
71  Пенсионер
9   Ребёнок
58  Взрослый

Условная последовательность — по-прежнему один столбец: вы читаете её как ${{AgeGroup}}, так же как простую. Несколько тегов <gen> только решают, какое значение заполнит каждую строку.

Зависимые последовательности (parent)

Последовательность может зависеть от другой через parent="Parent.Value". Дочерняя вычисляется только на строках, где совпадает родитель, а любые проценты внутри неё считаются от этого подмножества, а не от всего count. В этом — сердце иерархической модели TDC:

<sequence name="Gender">
<gen type="text" value="Мужчина,Женщина" percent="50,50"/>
</sequence>

<sequence name="MaleName" parent="Gender.Мужчина">
<gen type="template" value="person.male.firstName"/>
</sequence>

<sequence name="FemaleName" parent="Gender.Женщина">
<gen type="template" value="person.female.firstName"/>
</sequence>

MaleName материализуется только на строках, где Gender == Мужчина; на остальных он пуст. Полный разбор — вложенные проценты, несколько уровней — в разделе Иерархические зависимости.

Составная последовательность с parent

Составные и parent сочетаются напрямую. Здесь каждый пол получает свою сгруппированную сущность, а мужское подмножество несёт дополнительное поле Rank:

<sequence name="Gender">
<gen type="text" value="Мужчина,Женщина" percent="50,50"/>
</sequence>

<sequence name="Male" parent="Gender.Мужчина">
<gen name="FirstName" type="template" value="person.male.firstName"/>
<gen name="LastName" type="template" value="person.male.lastName"/>
<gen name="Rank" type="text" value="Рядовой,Капитан" percent="70,30"/>
</sequence>

<sequence name="Female" parent="Gender.Женщина">
<gen name="FirstName" type="template" value="person.female.firstName"/>
<gen name="LastName" type="template" value="person.female.lastName"/>
</sequence>

<block>
<line if="Gender == Мужчина"><data>М: ${{Male.FirstName}} ${{Male.LastName}} (${{Male.Rank}})</data></line>
<line if="Gender == Женщина"><data>Ж: ${{Female.FirstName}} ${{Female.LastName}}</data></line>
</block>
./run ranks.tdc (count=6, seed=demo)
Ж: Нина Полякова
М: Кирилл Вишневский (Рядовой)
Ж: Алла Егорова
Ж: Инна Давыдова
М: Матвей Соловьёв (Капитан)
М: Олег Романов (Рядовой)

Что это гарантирует:

  • Все три поля Male.* материализуются только на мужских строках; на женских они пусты (и наоборот).
  • Male.Rank разбивается как 70% Рядовой, 30% Капитан внутри мужского подмножества — проценты считаются от числа мужчин, а не от общего count.

Детерминизм и порядок полей

Поля составной последовательности материализуются в порядке объявления, и каждый <gen> расходует ГПСЧ независимо. Отсюда одно следствие, которое стоит запомнить:

Перестановка двух тегов <gen name="…"> меняет вывод при том же сиде. Первое поле вычерпывает другой отрезок случайного потока, чем раньше, поэтому каждое поле после него сдвигается.

<!-- Эти два дают РАЗНЫЕ значения при одном и том же сиде -->
<sequence name="Person">
<gen name="FirstName" type="template" value="person.male.firstName"/>
<gen name="Age" type="number" value="18..60"/>
</sequence>

<sequence name="Person">
<gen name="Age" type="number" value="18..60"/>
<gen name="FirstName" type="template" value="person.male.firstName"/>
</sequence>

Если вам нужен байт-в-байт воспроизводимый вывод между прогонами, зафиксируйте порядок полей один раз и не переставляйте генераторы после того, как выбрали сид. Подробнее о том, что сид гарантирует, а что нет: Детерминизм и пропорции.

Смежные источники значений

Рядом с <sequence> в <env> живут ещё два именованных источника, которые тоже читаются через ${{Name}}:

  • <mix> — распределение по точным процентам, где каждая ветка может быть целым составным блоком из литералов и генераторов (а не одним значением).
  • <switch> — детерминированная таблица соответствий «ключ → значение».

Когда вам нужен просто один столбец случайных или взвешенных значений, инструмент — это <sequence>.

Дальше