Тег <distinct>
Когда применять — когда два поля в одной строке тянут из одного пула и не
должны совпасть по значению: два симптома одного пациента — ни у кого не бывает
температуры и температуры; страна рождения и страна проживания, которые не должны
оказаться одинаковыми. <distinct> говорит: его прямые дети в одной строке должны
получиться разными между собой.
Это горизонтальный механизм — он смотрит поперёк полей одной строки. Его
вертикальный близнец — uniq: он не даёт всей строке
повториться где-либо в датасете. Эти два механизма полностью независимы —
используйте любой из них или оба сразу.
Показанные ниже вытяжки — то, что даёт типичный запуск; они могут измениться в
зависимости от версии ядра и seed. Неизменной остаётся структура, которую
гарантирует <distinct>: никакие два ребёнка группы никогда не равны друг другу
в пределах строки.
- Aс distinct на 60 строках: диагональ пуста, потому что внутри строки поля не могут совпасть
- Bс uniq на 6 строках: ни в одной клетке не больше единицы, потому что сочетание не может повториться между строками — а пустые клетки это сочетания, до которых прогон просто не дошёл
Проблема: два поля сталкиваются
Возьмём два симптома одного пациента из одного списка. Два поля
<gen> работают независимо, так что рано или поздно
на какой-то строке выпадает одно и то же слово дважды — а «лихорадка, лихорадка» это
не карточка пациента, это дефект. Здесь оба поля используют короткий список из
четырёх, поэтому совпадения видны сразу:
<sequence name="Case">
<gen name="S1" type="text" value="Лихорадка,Кашель,Головная боль,Тошнота"/>
<gen name="S2" type="text" value="Лихорадка,Кашель,Головная боль,Тошнота"/>
</sequence>
...
<data>${{Case.S1}}, ${{Case.S2}}</data>
Тошнота, Головная боль Головная боль, Головная боль Лихорадка, Кашель Тошнота, Тошнота Головная боль, Лихорадка Кашель, Тошнота Кашель, Лихорадка Лихорадка, Кашель
Строки 2 и 4 — Головная боль, Головная боль и Тошнота, Тошнота: это не пациент с
двумя жалобами, а пациент с одной жалобой, записанной дважды.
Решение: обернуть поля
Оборачиваем оба поля в <distinct>. Всё остальное как было — те же генераторы,
тот же список, даже тот же seed:
<sequence name="Case">
<distinct>
<gen name="S1" type="text" value="Лихорадка,Кашель,Головная боль,Тошнота"/>
<gen name="S2" type="text" value="Лихорадка,Кашель,Головная боль,Тошнота"/>
</distinct>
</sequence>
Тошнота, Головная боль Головная боль, Тошнота Лихорадка, Кашель Тошнота, Кашель Головная боль, Лихорадка Кашель, Тошнота Кашель, Лихорадка Лихорадка, Кашель
Что мы получаем. Строки, где совпадения не было, остались байт-в-байт теми
же — движок их не трогал. Починены только два столкновения: Головная боль, Головная боль стало Головная боль, Тошнота, а Тошнота, Тошнота — Тошнота, Кашель. Перетянуто только второе поле и
только там, где было нужно. Порядок и seed сохранены.
Два уровня
<distinct> работает в двух местах, и правило на обоих одно: прямые дети
<distinct> дают разные значения в каждой строке. Меняется лишь то, что считается
«ребёнком».
1. Внутри <sequence> — оборачивает поля <gen>
Здесь <distinct> стоит внутри
<sequence> и оборачивает поля
<gen name="…">. Читается как «A ≠ B». Здесь данные берутся из настоящего списка
симптомов в пакете, а не из написанной вручную четвёрки:
<sequence name="Case">
<distinct>
<gen name="A" type="template" value="medical.symptom"/>
<gen name="B" type="template" value="medical.symptom"/>
</distinct>
</sequence>
Рвота + Головокружение Потеря аппетита + Рвота Затуманенное зрение + Насморк Отёк + Лихорадка Учащённое сердцебиение + Онемение Затуманенное зрение + Одышка
Почему здесь: оба поля тянут из одного пула
template, но два значения в каждой строке всегда
разные. Это самый частый случай — два атрибута одной сущности с общим источником,
которые не должны совпадать. Заметьте, что значение всё ещё может повторяться по
столбцу (Затуманенное зрение в строках 3 и 6): <distinct> смотрит поперёк
строки, а не вдоль набора.
2. Внутри <env> — оборачивает целые блоки <sequence>
На верхнем уровне <distinct> стоит внутри
<env> и оборачивает целые блоки
<sequence>. Классический пример — «страна рождения» против «страны проживания»:
две независимые вытяжки из одного списка стран иногда совпадают в одной строке.
<env count="100" seed="s">
<distinct>
<sequence name="Birth"><gen type="template" value="location.country"/></sequence>
<sequence name="Live"><gen type="template" value="location.country"/></sequence>
</distinct>
</env>
Франция → Бутан Панама → Чили Черногория → Япония Камерун → Кения Перу → Намибия Гватемала → Гренада
Почему здесь: теперь два значения живут в разных последовательностях, поэтому ни одна
отдельная последовательность не может их сравнить — группа должна стоять уровнем выше, в
<env>. Страна рождения и страна проживания в любой строке не совпадут.
Как это работает
Движок генерирует поля как обычно. Если два значения внутри группы в строке
столкнулись, он перетягивает одно из них следующим значением генератора,
повторяя, пока они не станут разными. Детерминизм сохраняется: перетяжки идут в
фиксированном порядке, поэтому вывод для заданного seed не меняется. Это
работает и в движке памяти, и в стриминге — см.
Большие объёмы.
Детали и подводные камни
Каждый из этих пунктов стоит держать в голове:
Сравниваются значения, а не источники
<distinct> смотрит на получившуюся строку, а не на то, откуда она взялась.
Если два поля читают из разных файлов, но выдали одно и то же слово, это всё равно
считается столкновением, и одно из них перетягивается.
Группы независимы
Групп <distinct> может быть несколько, и они не мешают друг другу. Группа для
двух симптомов и отдельная группа, скажем, для двух телефонов — каждая
обеспечивает своё правило без всякого пересечения.
<sequence name="Case">
<distinct>
<gen name="A" type="template" value="medical.symptom"/>
<gen name="B" type="template" value="medical.symptom"/>
</distinct>
<distinct>
<gen name="HomePhone" type="regex" value="\+7 \([0-9]{3}\) [0-9]{3}-[0-9]{4}"/>
<gen name="CellPhone" type="regex" value="\+7 \([0-9]{3}\) [0-9]{3}-[0-9]{4}"/>
</distinct>
</sequence>
Почему: каждая группа ограничивает только своё. Два симптома никогда не равны друг другу; домашний и мобильный телефон никогда не равны друг другу; но симптом свободно может совпасть с телефоном, который случайно отрендерился так же, — две группы не видят одна другую.
Поля вне <distinct> остаются без ограничений
Ограничены только прямые дети группы <distinct>. Любое поле, оставленное
снаружи, генерируется как обычно и может свободно повторить значение, которое
выдало distinct-поле.
Слишком мало значений — честная ошибка
Если в списке меньше разных значений, чем полей, которые должны различаться, —
скажем, одно слово на два поля, — ограничение выполнить невозможно. Чтобы не
крутиться в цикле вечно, TDC сдаётся после 1000 попыток на строке и говорит об
этом. В отличие от uniq, который доказывает выполнимость до генерации, здесь
отказ происходит уже во время прогона — быстро, но не до его начала:
tdc: stream mode: <distinct> in sequence "Person": could not find a value for field "B" different from the others after 1000 attempts — its source likely has too few distinct values.
Почему: невыполнимый запрос должен падать громко, а не зависать. От
uniq отличается момент: uniq доказывает
выполнимость для всего столбца до генерации, а <distinct> узнаёт об этом на первой же
строке, которую не может удовлетворить.
На уровне <env> группа принимает только последовательности с одним значением
<distinct> внутри <env> может оборачивать только последовательности с одним
значением — простой <gen>, <mix> или <switch>.
У составной (многополевой) последовательности нет единственного значения для сравнения,
поэтому положить её в группу нельзя — ошибка TDC129:
error[TDC129]: <sequence name="Person"> inside a config-level <distinct> must produce a single value note: A <distinct> around sequences uses one value per sequence. Use a simple <gen> or a <switch> sequence, not a compound (multi-field) one.
Почему: горизонтальному правилу нужно по одному значению на каждого ребёнка
для сравнения. Внутри последовательности оборачивайте поля <gen> напрямую (форма уровня 1
выше); на уровне <env> держите каждую сгруппированную последовательность с одним
значением.
<distinct> и uniq — коротко о разнице
| Механизм | Ось | Область | Смысл |
|---|---|---|---|
<distinct> | горизонтальная | одна строка | поля не равны друг другу в пределах строки |
uniq | вертикальная | все строки | сочетание полей никогда не повторяется |
Они решают разные задачи и свободно сочетаются — строка может требовать, чтобы два
её поля с именами различались, и чтобы вся пара (имя, фамилия) была уникальной
по всему датасету. О вертикальном правиле см.
Уникальные значения.
Может содержать
| Тег | Где | Что содержит |
|---|---|---|
<gen/> | внутри <sequence> | Поля, которые должны различаться |
<sequence> | внутри <env> | Последовательности, которые должны различаться |
См. также
- Уникальные значения —
uniq, вертикальный близнец: целые строки, которые никогда не повторяются по датасету. - Последовательности — составные последовательности и поля,
структуры, над которыми работает
<distinct>. - Детерминизм и пропорции — почему
фиксированный
seedвоспроизводит тот же вывод, вместе со всеми перетяжками.