Генератор 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:
RU 70 US 20 DE 10
Чтобы отмерить разбивку точно, столбец с взвешенным выбором строится целиком в
оперативной памяти. Для небольших и средних наборов это норма. Если нужны точные доли
при потоковой генерации (память O(1), выгрузка любого размера) — их так же дают
<mix percent> и
<gen type="text" percent="…">: они отдают
точные проценты потоком, не держа весь столбец в памяти. А если вы вручную форсируете
чисто потоковый движок (mode="stream" или --engine 2), 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 -->
веса 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
| Возможность | regex | advanced_regex |
|---|---|---|
| Сгенерировать строку по паттерну | да | да |
| Character classes | да | да |
| Именованные Unicode-алфавиты | да | да |
| Groups и backreferences | да | да |
Ограничение длины через regex_max_length | да | да |
| Точные проценты внутри паттерна | нет | да |
| Вкладывать процентные варианты | нет | да |
| weighted-choice в блоке вывода | нет | нет |
Разницу проще всего увидеть рядом. Обычный regex выбирает каждый символ
свободно — форма фиксирована, доли нет:
<gen type="regex" value="[A-Z]{2}[0-9]{6}"/>
SA701363 RV926087 GG609313 GS428409 DQ957920
advanced_regex сохраняет ту же природу кода, но закрепляет доли префиксов — 70% RU,
20% US, 10% DE на всём прогоне:
<gen type="advanced_regex" value="(?%{70:RU;20:US;10:DE})-[0-9]{6}"/>
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}"/>
WJ-0170 FJ-7879 QN-6827 ABX-2477 WJ-1020 QNP-2747 FJ-8026
Unicode-демо. Поскольку ветки принимают именованные алфавиты, можно держать точные доли между письменностями. Здесь 7 из 10 кодов получают кириллический префикс, а 3 — латинский; это намеренный пример по Unicode/локализации, показывающий, что механика точных процентов не зависит от письменности:
<gen type="advanced_regex" value="(?%{70:\a{cyrillic.ru.upper}{2};30:\a{latin.upper}{2}})-[0-9]{4}"/>
ЭЗ-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":
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":
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 мужских строк):
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"/>
702-BC-702 682-FR-682 220-BY-220 277-FW-277 165-NS-165
Взвешенную ветку можно захватить и повторить через \1 — захваченная часть
воспроизводится дословно, и проценты сохраняются. При count="40":
<gen type="advanced_regex" value="((?%{25:AB;75:CD}))-\1"/>
AB-AB 10 CD-CD 30
Захваченная пара повторяется буквально, а разбивка 25/75 сохраняется.
Захват, сделанный до взвешенного выбора, можно использовать внутри ветки. Здесь
половина строк повторяет захваченные две буквы, половина печатает фиксированное XX
(count="8"):
<gen type="advanced_regex" value="([A-W]{2})-(?%{50:\1;50:XX})"/>
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"/>
A8-A8 B- A5-A5 B- B- A8-A8 B-
Это ещё не полноценный if, но уже полезная логическая связь: одна часть строки может
зависеть от уже сгенерированной группы.
Экранирование внутри weighted-choice
;, } и : — управляющие символы взвешенного выбора. Чтобы использовать их как
буквальный текст в ветке, экранируйте их (count="6"):
<gen type="advanced_regex" value="(?%{50:A\;\}\:;50:B})"/>
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:
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}"/>
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"/>
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}})"/>
длина 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 -->
Обе отклоняются ещё до генерации — например, вторая:
error: invalid advanced_regex generator pattern: unbounded "+"
quantifier is not allowed; use "{1,n}"Запланировано, но ещё не реализовано
Следующее описывает направление развития, но не является валидным синтаксисом сегодня. Именованные захваты:
(?<sex>(?%{50:male;50:female}))
Условия, ветки которых должны быть полноценными выражениями advanced_regex, чтобы в
них можно было вкладывать взвешенные выборы и другие условия:
(?if{sex=male:MR;sex=female:MS})
Пока они не вышли, моделируйте логику между полями через фильтр
parent или через
<mix>.
См. также
- Regex — конечные конструкции, которые наследует эта страница.
- Symbol — именованные алфавиты
(
\a{name}). regex_max_lengthв справочнике атрибутов.<mix>— точные проценты для структурно разных веток.