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

Блок <mix>

Когда использовать — когда доли в столбце не равны и каждая доля больше одного слова. Настоящие данные перекошены: большинство заказов — «оплачено», а «отменено» — единицы; большинство аккаунтов бесплатные, премиум — редкость. Нужен столбец, где варианты идут в фиксированной пропорции — не поровну и не случайным шумом.

<mix> — это распределение: именованный источник, который раскладывает свои варианты — ветки <case> — по строкам в точных процентах. По сути это <sequence>, значения которой распределены по percent, только каждая ветка может быть целым набором литералов и генераторов, а не одним простым значением. Раскладка детерминирована для заданного seed.

Примеры вывода ниже иллюстративны — конкретные значения, которые даёт тот или иной seed, могут меняться между версиями ядра, но количества, которые гарантирует маска percent, — никогда.

Заявленное против полученного на 1000 строках. Не «примерно» — количества попадают в заявленные доли точно.
  • проценты, написанные в конфиге
  • доля, которая получилась на самом деле

Именованное распределение

<mix> стоит прямо в <env>, рядом с <sequence> — обёртка не нужна, достаточно задать name. К значению обращаются через ${{Имя}}, как к любому другому именованному источнику:

<env count="100" seed="demo" inject="${{%}}">
<mix name="Code" percent="25,70">
<case><gen type="text" value="A"/></case>
<case><gen type="text" value="B"/></case>
<case><gen type="text" value="C"/></case>
</mix>
</env>
<block>
<line><data>${{Code}}</data></line>
</block>

Первые строки ничего не говорят — пропорции видны только на всей выборке:

./run code.tdc (первые 6 строк)
A
B
A
B
B
A

Посчитаем все 100 строк — раскрой точный:

./run code.tdc (count=100, с подсчётом)
A   25
B   70
C    5

Ровно 25 / 70 / 5. Маска задаёт первые две доли; третья ветка получает остаток, 100 − 25 − 70 = 5. Это не «примерно 25 %», а точная раскладка методом Хэмилтона (наибольших остатков) — тем же, что управляет percent у генератора text.

Поменяли проценты — поменялись доли

Тот же набор веток A/B/C, но percent="60,30":

<mix name="Code" percent="60,30">
<case><gen type="text" value="A"/></case>
<case><gen type="text" value="B"/></case>
<case><gen type="text" value="C"/></case>
</mix>
./run code.tdc (count=100, с подсчётом)
A   60
B   30
C   10

60 / 30, а остаток 10 уходит третьей ветке. Одно число в маске перекраивает весь столбец.

Когда нужен <mix>, а когда достаточно text

Для простого распределения готовых строк <mix> избыточен: генератор text уже раскладывает точные доли через percent.

<sequence name="Gender">
<gen type="text" value="Мужчина,Женщина" percent="50,50"/>
</sequence>
./run gender.tdc (count=20, с подсчётом)
Мужчина  10
Женщина  10

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

Атрибуты

АтрибутОбязательныйЧто делает
nameдаИмя для интерполяции через ${{Имя}}
percentнетДоля каждого <case>; опустите — получите равномерное деление
parentнетРодительская последовательность — доли считаются внутри её подмножества
flagнетДобавляет колонку-ответ, отмечающую ветку-выброс (см. ниже)
commentнетСвободная заметка для автора конфига; в вывод не попадает

В <mix> должна быть хотя бы одна <case> — одна ветка. Всё остальное необязательно.

percent — необязательный, и короткие маски

Опустите percent полностью — и ветки поделятся равномерно. Три кейса на 99 строк дают по 33 в каждом:

<mix name="Bucket">
<case><gen type="text" value="low"/></case>
<case><gen type="text" value="mid"/></case>
<case><gen type="text" value="high"/></case>
</mix>
./run bucket.tdc (count=99, с подсчётом)
low    33
mid    33
high   33

Если маску всё-таки задаёте, она подчиняется той же грамматике, что и percent у text: число фиксирует долю своей ветки, а каждая пустая позиция — голая запятая внутри маски ("25,,70") или хвостовая запятая ("25,70,") — делит остаток от 100 поровну. Оба примера в начале страницы уже опираются на это правило: percent="25,70" оставляет третьей ветке остаток.

parent — распределение внутри подмножества

Задайте <mix> атрибут parent — и проценты считаются против отфильтрованного подмножества строк, а не всего count. Это то же правило, что управляет зависимой <sequence>. Здесь платные аккаунты получают разбивку по уровням, а бесплатные оставляют столбец пустым:

<sequence name="Segment">
<gen type="text" value="Free,Paid" percent="70,30"/>
</sequence>

<mix name="Tier" parent="Segment.Paid" percent="60,30">
<case><gen type="text" value="Silver"/></case>
<case><gen type="text" value="Gold"/></case>
<case><gen type="text" value="Platinum"/></case>
</mix>

При count="100" строк Paid получается 30. Маска 60 / 30 применяется к этим 30 строкам, поэтому Tier делится 18 / 9 / 3:

./run tier.tdc (только строки Paid, с подсчётом)
Silver     18
Gold        9
Platinum    3

18 + 9 + 3 = 30 — всё платное подмножество, а не процент от общей сотни. Это сердце иерархической модели; полный разбор, с вложенными уровнями, — в статье Иерархические зависимости.

<mix> вкладываются друг в друга

Внутри <case> может стоять вложенный <mix>, и вложенное деление считается против строк, выбравших внешнюю ветку — то же правило подмножества, на уровень глубже. Здесь треть всех строк — error, и внутри них внутренний <mix> градуирует тяжесть:

<mix name="Status" percent="34,33">
<case>
<gen type="text" value="ok"/>
</case>
<case>
<gen type="text" value="warn"/>
</case>
<case>
<mix percent="70,20">
<case><data>error/minor</data></case>
<case><data>error/major</data></case>
<case><data>error/fatal</data></case>
</mix>
</case>
</mix>

При count="100" внешнее деление — 34 ok / 33 warn / 33 error. Внутренняя маска 70 / 20 затем делит эти 33 строки error на 23 / 7 / 3:

./run status.tdc (count=100, с подсчётом)
ok            34
warn          33
error/minor   23
error/major    7
error/fatal    3

23 + 7 + 3 = 33 — ровно подмножество ошибок.

То же верно уровнем выше: <mix>, записанный внутри ветки <switch>, берёт квоту по строкам, которые эта ветка совпала, а не по всему прогону.

Составные ветки

Вот что даёт <mix> и чего не даст голый список строк: <case> может собрать своё значение из нескольких кусков — литерального фрагмента <data>, одного или нескольких генераторов и вложенных <mix>. В этом контексте <data> — просто литеральный кусок значения («клей»), а не форматирование вывода; о форматировании см. Маски и регистр.

<mix name="Charge" percent="10,12,34,">
<case><data>возврат: </data><gen type="number" value="1..10"/></case>
<case><data>чарджбэк: </data><gen type="number" value="11..20"/></case>
<case><gen type="number" value="21..40"/></case>
<case><gen type="number" value="41..100"/><data> (помечено)</data></case>
</mix>
./run charge.tdc (первые 8 строк)
36
возврат: 4
чарджбэк: 18
66 (помечено)
возврат: 8
возврат: 1
73 (помечено)
82 (помечено)

Каждая ветка собрала свою форму: возврат: 4 — это литерал возврат: плюс number в диапазоне 1..10; 66 (помечено) — число из 41..100, за которым идёт литерал (помечено); третья ветка — голое число без всякой обёртки. Свернём 100 строк по веткам — доли по-прежнему точны:

./run charge.tdc (count=100, с подсчётом по веткам)
возврат     10
чарджбэк    12
чистое      34
помечено    44

10 / 12 / 34 / 44. У последней ветки нет собственного процента — хвостовая запятая в percent="10,12,34," оставляет её открытой, поэтому она берёт остаток, 44.

Распределение генерирует — оно не форматирует

<mix> порождает данные, поэтому живёт только в <env>. Поставить его в блок вывода нельзя: <mix> прямо в <line> отклоняется ещё до запуска с ошибкой TDC132, потому что блок вывода отвечает только за раскладку. Объявите <mix name="…"> в <env> и подставьте ${{Имя}} там, где нужно значение.

Если нужен выбор не по проценту, его покрывают два соседа:

  • <switch> выбирает по ключу — таблица соответствий, вроде страна → валюта.
  • <sequence> с условными ветками <gen if="…"> выбирает по произвольному условию — побеждает первая истинная ветка.

Пометка выбросов через flag

Ветку можно объявить аномальной — <case anomaly="true"> — и попросить у mix колонку-ответ через flag. В результате получается набор данных, на котором можно проверить детектор аномалий: и выбросы есть, и соседний столбец записывает, на какие именно строки они пришлись.

<mix name="Temp" percent="75,25" flag="Bad">
<case><gen type="number" value="20..24"/></case>
<case anomaly="true"><gen type="number" value="90..99"/></case>
</mix>

${{Bad}} равно true ровно на тех строках, что пришли из помеченной ветки — на 25 % строк, — и false на всех остальных. Метка выводится из того же решения, которым выбиралась ветка, поэтому разойтись со значением она не может. anomaly="true" — всего лишь ярлык: сам выброс — это то, что выдаёт генератор ветки, поэтому вы полностью управляете тем, как он выглядит. Это лишь уголок более широкой темы — инъекция выбросов и колонка flag получают полный разбор в руководстве по аномалиям.

Дальше

  • Генератор text — точные доли percent для простого списка вариантов, когда ветки — отдельные слова.
  • Последовательности — именованный источник, рядом с которым стоит <mix>, и модель parent, которую они делят.
  • Иерархические зависимости — полная история про parent, со вложенными процентами на нескольких уровнях.