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. Las dos son totalmente independientes: use
cualquiera, o ambas a la vez.
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.
- 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>
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>
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>
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>
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:
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:
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.
<distinct> frente a uniq, de un vistazo
| Mecanismo | Eje | Alcance | Significado |
|---|---|---|---|
<distinct> | horizontal | una fila | los campos no son iguales entre sí dentro de la fila |
uniq | vertical | todas las filas | la combinación de campos nunca se repite |
Resuelven problemas distintos y se combinan sin problema — una fila puede exigir que sus
dos campos de nombre difieran y que el par completo (first, last) sea único en todo
el dataset. Para la regla vertical, vea Valores únicos.
Puede contener
| Etiqueta | Dónde | Qué contiene |
|---|---|---|
<gen/> | dentro de <sequence> | Campos que deben diferir |
<sequence> | dentro de <env> | Secuencias que deben diferir |
Véase también
- Valores únicos —
uniq, 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
seedfijo reproduce la misma salida, con reextracciones y todo.