Saltar al contenido principal

La etiqueta <distinct>

Conviene usarla cuando dos campos de la misma fila sacan del mismo conjunto y no deben caer en el mismo valor: un primer nombre y un segundo nombre que no deberían salir José José, un país de nacimiento y un país de residencia que no deberían ser idénticos. <distinct> dice: sus hijos directos, dentro de una fila, deben diferir entre sí.

Esta es una regla horizontal — mira a lo ancho de los campos de una sola fila. Su gemela vertical es uniq, que evita que la fila completa se repita en cualquier parte del dataset. Use cualquiera, o ambas en una misma configuración sobre campos distintos: sobre los mismos campos la combinación se rechaza (TDC267).

Las salidas de ejemplo son ilustrativas

Las extracciones exactas de abajo son las que produce una corrida típica; pueden cambiar según la versión del core y el seed. Lo que nunca cambia es la estructura que garantiza <distinct>: dos hijos de un grupo jamás son iguales dentro de una fila.

Con qué frecuencia salió cada combinación de dos campos. En horizontal: el primer campo; en vertical: el segundo.
  • Acon distinct, sobre 60 filas: la diagonal queda vacía, porque una fila nunca puede repetir un valor entre sus campos
  • Bcon uniq, sobre 6 filas: ninguna celda pasa de 1, porque una combinación nunca puede repetirse entre filas — las celdas vacías son combinaciones a las que esta ejecución simplemente no llegó

El problema: dos campos colisionan

Tome un primer nombre y un segundo nombre de la misma lista. Los dos campos <gen> corren de forma independiente, así que tarde o temprano alguna fila saca la misma palabra dos veces. Aquí ambos campos usan una lista corta de cuatro nombres, así que las colisiones salen a la luz de inmediato:

<sequence name="Person">
<gen name="First" type="text" value="José,Antonio,Juan,Manuel"/>
<gen name="Middle" type="text" value="José,Antonio,Juan,Manuel"/>
</sequence>
...
<data>${{Person.First}} ${{Person.Middle}}</data>
./run person.tdc (8 rows)
José Manuel
Antonio José
Manuel Juan
Juan Juan
Manuel Antonio
José José
Juan Antonio
Antonio Manuel

Las filas 4 y 6 son Juan Juan y José José — una persona con el mismo primer y segundo nombre no debería existir en los datos.

La solución: envolver los campos

Envuelva ambos campos en <distinct>. Todo lo demás queda igual: los mismos generadores, la misma lista, incluso el mismo seed:

<sequence name="Person">
<distinct>
<gen name="First" type="text" value="José,Antonio,Juan,Manuel"/>
<gen name="Middle" type="text" value="José,Antonio,Juan,Manuel"/>
</distinct>
</sequence>
./run person.tdc (8 rows)
José Manuel
Antonio José
Manuel Juan
Juan José
Manuel Antonio
José Juan
Juan Antonio
Antonio Manuel

La ganancia. Las filas que no tenían colisión quedan idénticas byte por byte — el motor no las tocó. Solo se repararon las dos colisiones: Juan Juan pasó a Juan José y José José pasó a José Juan. Únicamente se volvió a extraer el segundo campo, y solo donde hacía falta. El orden y el seed se conservan.

Dos niveles

<distinct> funciona en dos lugares, con la misma regla en ambos: los hijos directos de <distinct> producen valores distintos en cada fila. Lo que cambia es qué cuenta como «hijo».

1. Dentro de una <sequence> — envuelve campos <gen>

Aquí <distinct> va dentro de una <sequence> y envuelve los campos <gen name="…">. Se lee como «First ≠ Middle»: José Manuel se permite, José José no.

<env count="6" seed="es" local="es">
<sequence name="Person">
<distinct>
<gen name="First" type="template" value="person.male.firstName"/>
<gen name="Middle" type="template" value="person.male.firstName"/>
</distinct>
</sequence>
</env>
./run person.tdc (6 rows) — First Middle
Gustavo Mateo
Esteban Máximo
Daniel Aurelio
Edgar Eleuterio
Elpidio Florencio
Jeremías José

Por qué aquí: ambos campos sacan del mismo conjunto de nombres de template y, aun así, los dos valores de cada fila siempre difieren. Este es el caso de todos los días — dos atributos de una misma entidad que comparten fuente pero no deben coincidir.

2. Dentro de <env> — envuelve bloques <sequence> completos

En el nivel superior, <distinct> va dentro de <env> y envuelve bloques <sequence> enteros. El ejemplo clásico es «país de nacimiento» contra «país de residencia»: dos extracciones independientes de la misma lista de países coinciden de vez en cuando dentro de una fila.

<env count="100" seed="s" local="es">
<distinct>
<sequence name="Birth"><gen type="template" value="location.country"/></sequence>
<sequence name="Live"><gen type="template" value="location.country"/></sequence>
</distinct>
</env>
./run migration.tdc (6 rows) — Birth -> Live
Portugal -> Polonia
Vietnam -> Sudán del Sur
San Cristóbal y Nieves -> Hungría
Vanuatu -> Egipto
Samoa -> Siria
Antigua y Barbuda -> Isla de Navidad

Por qué aquí: los dos valores viven ahora en secuencias separadas, así que ninguna secuencia por sí sola puede compararlos — el grupo tiene que subir un nivel, hasta <env>. El país de nacimiento y el país de residencia de una misma fila nunca coinciden.

Cómo funciona

El motor genera los campos como siempre. Si dos valores dentro de un grupo colisionan en una fila, vuelve a extraer uno de ellos con el siguiente valor del generador, y repite hasta que difieran. El determinismo se conserva: las reextracciones corren en un orden fijo, así que la salida para un seed dado no cambia. Funciona igual en el motor en memoria que en streaming — vea Salidas grandes.

Detalles y trampas

Cada uno de estos puntos es una regla que conviene tener presente:

Se comparan los valores, no las fuentes

<distinct> mira la cadena producida, no de dónde vino. Si dos campos leen de archivos distintos pero resulta que emiten la misma palabra, sigue contando como colisión y uno de ellos se vuelve a extraer.

Los grupos son independientes

Puede tener varios bloques <distinct> y no se estorban entre sí. Un grupo para el primer y segundo nombre y otro grupo aparte para, digamos, dos números de teléfono imponen cada uno su propia regla, sin interferencias.

<sequence name="Person">
<distinct>
<gen name="First" type="template" value="person.male.firstName"/>
<gen name="Middle" type="template" value="person.male.firstName"/>
</distinct>
<distinct>
<gen name="HomePhone" type="regex" value="\+52 \([0-9]{3}\) [0-9]{3}-[0-9]{4}"/>
<gen name="CellPhone" type="regex" value="\+52 \([0-9]{3}\) [0-9]{3}-[0-9]{4}"/>
</distinct>
</sequence>

Por qué: cada grupo delimita su propia restricción. El primer y el segundo nombre nunca pueden ser iguales entre sí; el teléfono de casa y el celular tampoco; pero un nombre queda libre de coincidir con una serie de dígitos que se renderice igual — los dos grupos no se ven el uno al otro.

Los campos fuera de <distinct> no conservan restricción

Solo los hijos directos de un grupo <distinct> quedan restringidos. Cualquier campo que se deje afuera se genera con normalidad y puede repetir libremente un valor que haya producido un campo del grupo.

Con muy pocos valores falla de forma limpia

Si una lista tiene menos valores distintos que la cantidad de campos que deben diferir —digamos, una sola palabra para dos campos—, la restricción es imposible de satisfacer. En vez de quedarse en un ciclo infinito, TDC se rinde tras 1000 intentos en una fila y lo dice. A diferencia de uniq, que comprueba la viabilidad antes de generar, este falla durante la corrida — rápido, pero no antes de que empiece:

./run person.tdc
tdcv2: 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.

Por qué: una petición imposible debe fallar de forma ruidosa, no colgarse. Lo que cambia frente a uniq es el momento: uniq prueba la viabilidad de toda la columna antes de generar, mientras que <distinct> se entera en la primera fila que no puede satisfacer.

En el nivel <env>, los grupos aceptan solo secuencias de un valor

Un <distinct> dentro de <env> puede envolver únicamente secuencias de un solo valor — un <gen> simple, un <mix> o un <switch>. Una secuencia compuesta (de varios campos) no tiene un valor único que comparar, así que ponerla en el grupo se rechaza con el error TDC129:

./run migration.tdc
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.

Por qué: la regla horizontal necesita un valor por hijo para poder comparar. Dentro de una secuencia, envuelva directamente los campos <gen> (la forma de nivel 1 de arriba); en el nivel <env>, mantenga cada secuencia agrupada en un solo valor.

Dos referencias a un mismo <pool> — comparadas por REGISTRO, no por valor

Una secuencia con <gen type="pool"> es la excepción, y es justo el caso para el que más se escribe el grupo: dos personas sacadas del mismo pool que no deben ser la misma.

Una referencia guarda un miembro entero, así que no hay valor que comparar — el grupo compara qué miembro tomó la fila. Ponga las referencias en el grupo y ninguna fila entrega el mismo registro a dos de ellas:

<pool name="Doctors" count="6">
<sequence name="name" uniq="true"><gen type="text" value="Adams,Brooks,Chase,Dunn,Ellis,Frost"/></sequence>
</pool>
<distinct>
<sequence name="Duty"><gen type="pool" value="Doctors"/></sequence>
<sequence name="Seen"><gen type="pool" value="Doctors"/></sequence>
</distinct>
./run clinic.tdc (6 filas, seed=clinic)
on duty: Chase | seen by: Ellis
on duty: Brooks | seen by: Frost
on duty: Brooks | seen by: Chase
on duty: Dunn | seen by: Brooks
on duty: Ellis | seen by: Frost
on duty: Adams | seen by: Frost

La comparación es por identidad, no por apellido: dos miembros que coincidieran en algún campo seguirían siendo dos registros distintos, y el grupo los separa por eso.

Se combina con filter=. El filtro decide qué miembros puede tener esta fila; el grupo reparte a cada referencia uno distinto de ésos. Ambas promesas se cumplen a la vez.

Tres formas se rechazan con TDC302, porque ninguna puede significar nada:

  • una referencia junto a una secuencia corriente — una guarda un registro y la otra una cadena, y no hay campo sobre el que comparar;
  • referencias a pools distintos — un médico nunca es el mismo registro que una sala, así que el grupo se cumpliría sin hacer nada;
  • más referencias que miembros tiene el pool — no existe tal disposición.

<uniq> en el nivel <env> trabaja con referencias del mismo modo, un eje más allá: ninguna fila toma la misma combinación de miembros. Un pool de 6 ofrece 36 pares ordenados, de modo que una ejecución más larga que los pares que produjo el sorteo se rechaza de antemano en vez de repetirse en silencio.

uniq cumple esa promesa reordenando los sorteos, y un reordenamiento necesita la columna entera — no se puede hallar fila a fila. Por eso esta forma corre en el motor en memoria, igual que un total acumulado; <distinct> se resuelve por fila y fluye sin problema.

<distinct> frente a uniq, de un vistazo

MecanismoEjeAlcanceSignificado
<distinct>horizontaluna filalos campos no son iguales entre sí dentro de la fila
uniqverticaltodas las filasla combinación de campos nunca se repite

Resuelven problemas distintos, y una misma configuración puede usar ambas — sobre campos distintos. Sobre los mismos campos check lo rechaza con TDC267: uniq reordena las columnas terminadas sin saber qué pares descartó la reparación, así que la deshace. Para la regla vertical, vea Valores únicos.

Puede contener

EtiquetaDóndeQué contiene
<gen/>dentro de <sequence>Campos que deben diferir
<sequence>dentro de <env>Secuencias que deben diferir

Véase también

  • Valores únicosuniq, la gemela vertical: filas completas que nunca se repiten en el dataset.
  • Secuencias — las secuencias compuestas y los campos, las estructuras sobre las que opera <distinct>.
  • Determinismo y proporciones — por qué un seed fijo reproduce la misma salida, con reextracciones y todo.