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

Генератор 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" или --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 -->
./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
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}"/>
./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
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}"/>
./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
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"/>
./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
A8-A8
B-
A5-A5
B-
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}"

Запланировано, но ещё не реализовано

Следующее описывает направление развития, но не является валидным синтаксисом сегодня. Именованные захваты:

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

Условия, ветки которых должны быть полноценными выражениями advanced_regex, чтобы в них можно было вкладывать взвешенные выборы и другие условия:

(?if{sex=male:MR;sex=female:MS})

Пока они не вышли, моделируйте логику между полями через фильтр parent или через <mix>.

См. также

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