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

Генератор advanced_regex

Используйте его, когда нужно всё, что умеет regex, но сама форма строки несёт статистическое распределение вариантов — например, ровно 70% кодов начинаются с RU, 20% — с US, 10% — с DE.

Обычный regex выбирает альтернативу (RU|US|DE) случайно, поэтому разбивка сходится только в среднем. advanced_regex добавляет взвешенный выбор (weighted choice), который раскладывает варианты на точные количества.

<gen type="advanced_regex" value="(?%{70:RU;20:US;10:DE})-[0-9]{6}"/>

advanced_regex — надмножество regex: обычный генератор type="regex" остаётся стабильным и TDC-нейтральным, а advanced_regex добавляет TDC-специфичную мощь поверх того же конечного, переносимого движка. Сегодня эта дополнительная мощь — ровно одна конструкция, взвешенный выбор, а всё остальное на этой странице унаследовано без изменений из Regex.

Зачем это нужно

Без взвешенного выбора задачу «70% RU, 20% US, 10% DE, у каждого после префикса ещё шесть случайных цифр» пришлось бы описывать несколькими последовательностями или через <mix>. С advanced_regex это сворачивается в один генератор. Он естественно подходит для:

  • кодов стран, филиалов, регионов или типов клиентов;
  • тестовых идентификаторов с разной структурой, но фиксированными пропорциями;
  • номеров документов, где одна часть строки должна повторять другую;
  • синтетических данных, где доли важны не меньше самих значений;
  • AI-генерации .tdc, когда агенту проще выдать один компактный паттерн, чем дерево последовательностей.

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

Weighted-choice

Конструкция такая:

(?%{PERCENT:BRANCH;PERCENT:BRANCH;...})

Разбор по частям:

(?%{ 70:RU ; 20:US ; 10:DE })
│ │ │ │ │ │ │ │
│ │ ветка │ ветка │ ветка │
│ процент процент процент │
└── начало weighted-choice ┘

Правила:

  • проценты должны быть числами и неотрицательными;
  • сумма процентов должна быть равна 100;
  • ветка может быть пустой;
  • ветка сама является полноценным выражением advanced_regex (значит, они вкладываются);
  • ;, } и : — управляющие символы; экранируйте их (см. раздел «Экранирование внутри weighted-choice»), если они нужны в ветке буквально.

Точные проценты по умолчанию

Это главное: взвешенный выбор обещает точные проценты (ровно 70 из 100, а не «примерно 70»), и TDC выдаёт их из коробки — специально ничего включать не нужно.

Чтобы отмерить разбивку точно, движок строит весь столбец разом и раздаёт ветки по алгоритму Гамильтона (наибольшего остатка). Он делает это автоматически, как только в паттерне встречается (?%{…}) — даже в режиме по умолчанию disk. От вас нужен только паттерн, доли получатся ровными.

<env count="100" seed="countries">
<sequence name="CountryCode">
<gen type="advanced_regex" value="(?%{70:RU;20:US;10:DE})-[0-9]{2}"/>
</sequence>
</env>

Считаем префиксы на всех 100 строках — ровно 70/20/10:

./run countries.tdc (100 строк, по префиксу)
RU   70
US   20
DE   10
Цена точности — память

Чтобы отмерить разбивку точно, столбец с взвешенным выбором строится целиком в оперативной памяти. Для небольших и средних наборов это норма. Если нужны точные доли при потоковой генерации (память O(1), выгрузка любого размера) — их так же дают <mix percent> и <gen type="text" percent="…">: они отдают точные проценты потоком, не держа весь столбец в памяти. А если вы вручную форсируете чисто потоковый движок (mode="stream" на <env> — легаси-алиас, который существует только как атрибут, — или --engine 2 в командной строке; --mode stream не бывает), TDC не станет молча портить проценты — посчитать их построчно нельзя, поэтому он честно откажется с понятной ошибкой. Уберите форс — и всё снова точно.

Веса действительно сдвигают распределение

Проценты — не украшение, а реальный состав столбца. Возьмём один паттерн (?%{…})-[0-9]{2} при count="1000", поменяем только веса и посчитаем префиксы:

<gen type="advanced_regex" value="(?%{70:RU;20:US;10:DE})-[0-9]{2}"/> <!-- вариант 1 -->
<gen type="advanced_regex" value="(?%{34:RU;33:US;33:DE})-[0-9]{2}"/> <!-- вариант 2 -->
<gen type="advanced_regex" value="(?%{10:RU;10:US;80:DE})-[0-9]{2}"/> <!-- вариант 3 -->
./run weights.tdc (1000 строк, по префиксу)
веса             RU     US     DE
70 / 20 / 10     700    200    100
34 / 33 / 33     340    330    330
10 / 10 / 80     100    100    800

На 1000 строк количества повторяют веса один в один. Поменяли веса — поменялся состав столбца.

regex против advanced_regex

Возможностьregexadvanced_regex
Сгенерировать строку по паттернудада
Character classesдада
Именованные Unicode-алфавитыдада
Groups и backreferencesдада
Ограничение длины через regex_max_lengthдада
Точные проценты внутри паттернанетда
Вкладывать процентные вариантынетда
weighted-choice в блоке выводанетнет

Разницу проще всего увидеть рядом. Обычный regex выбирает каждый символ свободно — форма фиксирована, доли нет:

<gen type="regex" value="[A-Z]{2}[0-9]{6}"/>
./run plain.tdc
FZ399441
YH481897
LR586083
YA900972
WT831899

advanced_regex сохраняет ту же природу кода, но закрепляет доли префиксов — 70% RU, 20% US, 10% DE на всём прогоне:

<gen type="advanced_regex" value="(?%{70:RU;20:US;10:DE})-[0-9]{6}"/>
./run coded.tdc (первые 8 из 100 строк)
RU-441627
RU-476822
RU-948319
US-450875
RU-398584
RU-131212
RU-418648
RU-830959

Коротко: используйте regex для строки заданной формы; используйте advanced_regex, когда сама форма несёт статистическое распределение.

Наследует весь regex-язык

Каждая конечная конструкция из regex работает и здесь — литералы, экранирование, character classes, BMP-диапазоны, именованные алфавиты \a{…}, \d/\w/\s и их инверсии, ., alternation, groups, backreferences, ограниченные квантификаторы и ограничение regex_max_length. Простая взвешенная разбивка над обычными латинскими кодами:

<gen type="advanced_regex" value="(?%{70:[A-Z]{2};30:[A-Z]{3}})-[0-9]{4}"/>
./run mixed.tdc
QY-3500
ZT-3381
GSK-1914
VO-5921
DW-7570
SO-1660
MSE-2247

Unicode-демо. Поскольку ветки принимают именованные алфавиты, можно держать точные доли между письменностями. Здесь 7 из 10 кодов получают кириллический префикс, а 3 — латинский; это намеренный пример по Unicode/локализации, показывающий, что механика точных процентов не зависит от письменности:

<gen type="advanced_regex" value="(?%{70:\a{cyrillic.ru.upper}{2};30:\a{latin.upper}{2}})-[0-9]{4}"/>
./run unicode.tdc (count=10)
ЭЗ-2477
WJ-0170
ЧП-8026
СЦ-1020
ЫЦ-2747
FJ-7879
РЛ-6827
ЩЕ-4485
ПВ-0297
UD-1550

Вложенные weighted-choice

Поскольку ветки — это полноценные выражения, взвешенные выборы вкладываются, и внутренняя разбивка считается внутри подмножества, которое попало во внешнюю ветку:

<gen type="advanced_regex" value="(?%{50:A(?%{80:X;20:Y});50:B})"/>

При count="100":

./run nested.tdc (100 строк)
AX   40
AY   10
B    50

80% от 50 строк A — это 40 строк AX; 20% — это 10 строк AY. Это поведение «проценты внутри подмножества» в точности совпадает с философией иерархии последовательностей в TDC.

Несколько weighted-choice в одном паттерне

<gen type="advanced_regex" value="(?%{60:M;40:F})-(?%{25:00;75:99})"/>

Каждое распределение отмеряется точно на текущих строках. При count="100":

./run two.tdc (100 строк)
M-99   44
F-99   31
M-00   16
F-00    9

Сложите по частям — и оба точны: M = 44 + 16 = 60 и F = 31 + 9 = 40 (60/40); 99 = 44 + 31 = 75 и 00 = 16 + 9 = 25 (75/25). Два независимых выбора, каждый разложен тем же методом точных процентов.

С parent-фильтром

advanced_regex живёт внутри обычной модели зависимостей последовательностей. Если последовательность отфильтрована через parent, проценты считаются только внутри отфильтрованного подмножества:

<sequence name="Gender">
<gen type="text" value="M,F" percent="50,50"/>
</sequence>

<sequence name="MaleCode" parent="Gender.M">
<gen type="advanced_regex" value="M-(?%{40:A;60:B})-[0-9]{2}"/>
</sequence>

При count="100" (50 мужских строк):

./run parent.tdc (100 строк)
F      50    (MaleCode пустой)
M-A    20
M-B    30

Разбивка 40/60 отмерена от 50 отфильтрованных строк, а не от всех 100.

Захваты и backreferences

Backreference повторяет уже сгенерированную группу. Это работает точно так же, как в обычном regex — первые три цифры повторяются в конце:

<gen type="advanced_regex" value="([0-9]{3})-[A-Z]{2}-\1"/>
./run backref.tdc
299-YZ-299
929-UE-929
462-VR-462
905-BC-905
876-JF-876

Взвешенную ветку можно захватить и повторить через \1 — захваченная часть воспроизводится дословно, и проценты сохраняются. При count="40":

<gen type="advanced_regex" value="((?%{25:AB;75:CD}))-\1"/>
./run branch-capture.tdc (40 строк)
AB-AB   10
CD-CD   30

Захваченная пара повторяется буквально, а разбивка 25/75 сохраняется.

Захват, сделанный до взвешенного выбора, можно использовать внутри ветки. Здесь половина строк повторяет захваченные две буквы, половина печатает фиксированное XX (count="8"):

<gen type="advanced_regex" value="([A-W]{2})-(?%{50:\1;50:XX})"/>
./run capture-in-branch.tdc
TV-XX
GR-GR
RN-XX
OU-OU
WM-WM
SS-XX
CL-XX
QG-QG

Backreference может жить внутри ветки. Где взята ветка (A[0-9]), \1 повторяет её захват; где взята ветка B, захвата нет, поэтому \1 пустой (count="20"):

<gen type="advanced_regex" value="(?%{40:(A[0-9]);60:B})-\1"/>
./run optional-capture.tdc
A2-A2
B-
B-
A1-A1
B-
A8-A8
B-

Это ещё не полноценный if, но уже полезная логическая связь: одна часть строки может зависеть от уже сгенерированной группы.

Экранирование внутри weighted-choice

;, } и : — управляющие символы взвешенного выбора. Чтобы использовать их как буквальный текст в ветке, экранируйте их (count="6"):

<gen type="advanced_regex" value="(?%{50:A\;\}\:;50:B})"/>
./run escape.tdc
A;}:
A;}:
B
B
A;}:
B

Ветка A\;\}\: печатает буквальные A;}:, а ветка B печатает просто B.

Где работает взвешенный выбор

Взвешенный выбор задаёт точную процентную разбивку, поэтому среда выполнения должна знать, сколько строк придётся на каждую ветку. Это известно везде, где вообще может стоять <gen>:

  • внутри <sequence> — из count или подмножества parent;
  • внутри <case> у <mix> — из размера этого кейса.

Оба варианта валидны:

<sequence name="CountryCode">
<gen type="advanced_regex" value="(?%{70:RU;20:US;10:DE})-[0-9]{2}"/>
</sequence>

<mix name="Country" percent="50,50">
<case><gen type="advanced_regex" value="(?%{70:RU;30:US})"/></case>
<case><data>-</data></case>
</mix>

В блоке вывода генераторов нет вовсе — <line> только форматирует, — поэтому взвешенный выбор туда не попадает. Чтобы вывести его в строке, объявите его как последовательность и вставьте через интерполяцию ${{Name}}:

<tdc>
<env count="100" seed="demo" inject="${{%}}">
<sequence name="Code">
<gen type="advanced_regex" value="(?%{70:A;30:B})-[0-9]{4}"/>
</sequence>
</env>
<block>
<line><data>code=${{Code}}</data></line>
</block>
</tdc>

Первые строки, где прогон делится ровно 70 A / 30 B:

./run code.tdc (первые строки из 100)
code=A-8870
code=B-2495
code=B-1961
code=A-8865
code=A-9221
code=A-3234

Порядок строк детерминированно перемешан по seed; итоги остаются точными.

Практические примеры

Код клиента по сегментуcount="1000", считаем префиксы:

<gen type="advanced_regex" value="(?%{80:REG;15:VIP;5:TEST})-[A-Z]{2}[0-9]{4}"/>
./run segment.tdc (1000 строк, по префиксу)
REG    800
VIP    150
TEST    50

Документ с повторяющимся блоком — первые три цифры и последние три цифры всегда совпадают, а середина делится 60% A / 40% B (count="100"):

<gen type="advanced_regex" value="([0-9]{3})-(?%{60:A;40:B})-\1"/>
./run doc.tdc (первые строки из 100)
924-B-924
419-B-419
788-A-788
692-B-692

Короткие и длинные технические коды — 85% коротких, 15% длинных (count="100"):

<gen type="advanced_regex" value="(?%{85:[A-Z]{2}[0-9]{2};15:[A-Z]{4}[0-9]{8}})"/>
./run codes.tdc (100 строк, по длине)
длина  4    85    (AB42)
длина 12    15    (ABCD12345678)

<mix> или advanced_regex?

Оба умеют точные проценты, но для разных задач.

Используйте <mix>, когда у веток разная структура — свой набор генераторов и литерального текста в каждой (а ещё <mix percent> даёт точные доли потоком, не держа столбец в памяти):

<mix name="Kind" percent="70,30">
<case><data>{"type":"regular"}</data></case>
<case><data>{"type":"vip","bonus":true}</data></case>
</mix>

Используйте advanced_regex, когда всё укладывается в один паттерн (точно по умолчанию; столбец строится в памяти — для очень больших выгрузок берите <mix percent>):

<gen type="advanced_regex" value="(?%{70:REG;30:VIP})-[0-9]{6}"/>

Невалидные паттерны

<gen type="advanced_regex" value="(?%{70:A;20:B})"/> <!-- сумма 90, нужна 100 -->
<gen type="advanced_regex" value="[a-z]+"/> <!-- бесконечная длина, как и в обычном regex -->

Обе отклоняются ещё до генерации — например, вторая:

./run bad.tdc
error: invalid advanced_regex generator pattern: unbounded "+"
quantifier is not allowed; use "{1,n}"

Имя для группы — (?<name>…)

У группы кроме номера может быть имя:

(?<sex>(?%{50:male;50:female}))

Это обычная захватывающая группа — \1 по-прежнему её читает — только с ярлыком. Ярлык нужен, чтобы что-то ДАЛЬШЕ по шаблону могло спросить, что эта группа выдала; об этом следующий раздел.

\k<name> повторяет группу по этому имени — так же, как в regex, — и это та же самая группа, которую повторяет \1:

<gen type="advanced_regex" value="(?<c>(?%{50:AB;50:CD}))-\k<c>"/>

Имя должно принадлежать группе, которая уже закрылась: это то же правило, которому следуют \1 и (?if{…}) — группа дальше по шаблону ещё ничего не выдала, чтобы это повторять.

Имя начинается с буквы или _ и состоит из букв, цифр и _. Двум группам одно имя нельзя: тогда (?if{sex=…}) стало бы броском монетки между ними. Это верно и когда одна из двух групп находится внутри другой, а не рядом с ней.

Чтобы одна часть следовала за другой — (?if{…})

Когда нужно: две части одного значения обязаны согласовываться. Всё остальное в этом генераторе решает значение из одной только случайности — поэтому шаблон мог описать индекс или идентификатор, но не обращение, которое совпадает с полом, выбранным двумя символами раньше. Раньше это означало бросить advanced_regex и пересобирать колонку через <switch>.

<gen type="advanced_regex" value="(?<sex>(?%{50:male;50:female}))/(?if{sex=male:Mr;sex=female:Ms})"/>
./run titles.tdc (count=8, seed=titles)
female/Ms
male/Mr
male/Mr
female/Ms
male/Mr
female/Ms
female/Ms
male/Mr

Каждая строка согласована сама с собой. За male никогда не идёт Ms.

Как это читается

(?if{имя=значение:ветка;имя=значение:ветка}). Ветки разделяются ;, каждая — это что проверяем, потом :, потом что выдаём. Они пробуются в написанном порядке, побеждает первая совпавшая.

Ветка — полноценное выражение advanced_regex, поэтому внутрь вкладываются и взвешенные выборы, и другие условия:

<gen type="advanced_regex" value="(?<country>(?%{60:RU;30:US;10:DE}))-(?if{country=RU:[0-9]{3};country=US:[A-Z]{3};*:[A-Z]{2}[0-9]})"/>
./run plates.tdc (count=10, seed=plates)
RU-683
US-NGS
DE-ZQ5
US-VNS
RU-867
RU-722
RU-372
RU-890
US-SEA
RU-589

* — ветка «во всех остальных случаях», здесь это строки DE. Без неё строка, не подошедшая ни под одну ветку, выдаёт на этом месте вообще ничего:

(?<c>(?%{50:a;50:b}))-(?if{c=zzz:NEVER}) → a- b- a- b- …

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

Чего оно не делает

  • Не читает группу, объявленную позже. Шаблон собирается слева направо, поэтому группа дальше по строке ещё ничего не выдала, и ветку нельзя было бы взять никогда. Это отклоняется, а не оставляется выдавать пустые половинки.
  • Не читает ничего, кроме именованной группы — ни другую колонку, ни ${{…}}. Для логики между колонками есть <switch> и фильтр parent; это же — про части ОДНОГО значения.
  • Не сравнивает ничего, кроме текста. Проверка — это имя=значение, точное совпадение с тем, что выдала группа. Значение не может содержать : — там начинается ветка.

Квоты остаются точными

Условие читает взвешенный выбор, но не трогает его. На 200 строках (?<c>(?%{70:RU;20:US;10:DE})) по-прежнему даёт ровно 140, 40 и 20 — и вторая половина каждой строки следует из её собственной первой.

См. также

  • Regex — конечные конструкции, которые наследует эта страница.
  • Symbol — именованные алфавиты (\a{name}).
  • regex_max_length в справочнике атрибутов.
  • <mix> — точные проценты для структурно разных веток.