Valores únicos
Los datasets reales tienen dos reglas distintas de «sin duplicados», y TDC ofrece una herramienta separada para cada una:
| Mecanismo | Alcance | Significado |
|---|---|---|
<distinct> | una fila | los campos no son iguales entre sí dentro de la fila |
uniq | todas las filas | la combinación de campos es única en todo el dataset |
Conviene verlas como gemelas sobre dos ejes. <distinct> trabaja en horizontal —
dentro de una sola fila, de modo que nunca sale José José ni «nació en París, vive en
París». uniq trabaja en vertical — a lo largo de todo el dataset, de modo que el
mismo par (nombre, apellido) nunca aparece dos veces. Son totalmente independientes:
use cualquiera de las dos, o ambas a la vez.
Los valores de abajo son los que produce una corrida típica. Las extracciones exactas
pueden cambiar según la versión del core y el seed; lo que queda fijo es la
estructura que garantiza cada herramienta (ninguna colisión dentro de la fila para
<distinct>, ninguna combinación repetida para uniq).
- 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ó
<distinct> — distintos dentro de una fila
Dos campos <gen> que sacan de la misma lista corren
de forma independiente, así que tarde o temprano alguna fila toma el mismo valor dos
veces. Aquí un primer nombre y un segundo nombre vienen ambos de una lista corta de
cuatro nombres, así que las colisiones aparecen 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. Se envuelven ambos campos en <distinct> — todo lo demás queda igual,
incluso el 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
Las filas que no tenían colisión quedan idénticas byte por byte — el motor no las
tocó. Las dos colisiones se repararon: Juan Juan pasó a Juan José y
José José pasó a José Juan. Solo se volvió a extraer el segundo campo, y solo
donde hacía falta; el orden y el seed quedan intactos.
Dos niveles
<distinct> funciona en dos lugares, con la misma regla en ambos: los hijos directos
de <distinct> producen valores distintos en cada fila.
1. Dentro de una <sequence> — envuelve los
campos <gen name="…">. Se lee como «First ≠ Middle»:
<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é
Ambos campos sacan del mismo conjunto de nombres y, aun así, los dos valores de cada fila siempre difieren.
2. Dentro de <env> — envuelve bloques
<sequence> completos. Un caso clásico es «país de nacimiento» contra «país de
residencia»: dos extracciones independientes de la misma lista de países caen de vez en
cuando en el mismo país 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
Ahora el país de nacimiento y el país de residencia de una misma fila nunca coinciden.
Cómo funciona, y los detalles
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 hasta
que difieran. El determinismo se conserva — las reextracciones ocurren en un orden fijo,
así que la salida para un seed dado no cambia. Funciona igual en el motor en memoria
que en streaming.
Detalles que vale la pena conocer:
- Se comparan los valores, no las fuentes. Si dos campos leen de archivos distintos
pero resulta que producen la misma palabra,
<distinct>vuelve a extraer de todos modos. - Los grupos son independientes. Un
<distinct>para el primer y segundo nombre y otro aparte para otra cosa no se estorban; puede tener varios. - Los campos fuera de
<distinct>no conservan restricción alguna. - 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 palabra para dos campos), TDC lanza un error claro en vez de quedarse en un ciclo infinito.
- En el nivel
<env>el grupo acepta solo secuencias de un valor — un<gen>simple, un<mix>o un<switch>. Una secuencia compuesta (de varios campos) se rechaza ahí con el errorTDC129.
uniq — la combinación nunca se repite
uniq="true" en una <sequence> compuesta
significa que la combinación de todos sus campos no se repite en ninguna parte del
dataset. (José, García) y (José, Ruiz) están bien; dos (José, García) no.
En una secuencia simple — un solo <gen> sin nombre — uniq="true" significa que
el propio valor no se repite: el sorteo corre sin reemplazo. Un pack ponderado
conserva su sentido (los nombres frecuentes tienen más probabilidad de entrar), pero
nada aparece dos veces. Cuando la fuente tiene menos valores distintos que registros, la
corrida se rehúsa por adelantado nombrando ambos números — nunca una repetición
silenciosa. Fuentes soportadas: listas text, packs template, columnas file y
rangos enteros simples (value="1..100000"); increment/decrement son únicos por
construcción. Un generador cuyos valores no pueden enumerarse (regex, date, …) se
rechaza con un mensaje que lo dice tal cual.
<sequence name="Person" uniq="true">
<gen name="first" type="template" value="person.male.firstName"/>
<gen name="last" type="template" value="person.lastName"/>
</sequence>
<block>
<line><data>${{Person.first}} ${{Person.last}}</data></line>
</block>
Ningún par (first, last) se repite. Con 200 nombres y 500 apellidos hay hasta 100 000
pares únicos; si pide más, obtiene un error honesto por adelantado (vea abajo).
Antes y después, en un conjunto diminuto
Dos campos con conjuntos diminutos — first ∈ {Ana, Beto} y last ∈ {Ruiz, León} — dan
solo 4 pares posibles. Se piden 4 filas.
Sin uniq (cada campo aleatorio por su cuenta):
<sequence name="P">
<gen name="first" type="text" value="Ana,Beto"/>
<gen name="last" type="text" value="Ruiz,León"/>
</sequence>
<block><line><data>${{P.first}} ${{P.last}}</data></line></block>
Ana Ruiz 2 Beto León 2
Las combinaciones se repiten: Ana Ruiz y Beto León salieron dos veces cada una,
mientras que Ana León y Beto Ruiz no aparecieron nunca. El azar no sabe nada de
unicidad.
Con uniq="true" (la misma configuración, con un atributo añadido):
<sequence name="P" uniq="true">
<gen name="first" type="text" value="Ana,Beto"/>
<gen name="last" type="text" value="Ruiz,León"/>
</sequence>
Ana León 1 Ana Ruiz 1 Beto León 1 Beto Ruiz 1
Los 4 pares, una vez cada uno, sin repeticiones.
Las proporciones se conservan
El motor solo reacomoda los valores de los campos entre filas; nunca cambia cuántos
hay de cada uno. Por eso una máscara percent sigue siendo
exacta — la unicidad y una distribución exacta conviven. percent="70,30" sigue
repartiendo 70/30 incluso mientras cada combinación se mantiene única.
La verificación de viabilidad — antes de generar
Antes de renderizar, TDC calcula si siquiera es posible formar count combinaciones
únicas con sus datos. Si no lo es, obtiene un error de inmediato, no horas después:
uniq: sequence "Person" requested 10000 unique combinations, but its data supports at most 5000. Add more values to a field, or lower the count.
El conjunto diminuto ilustra lo mismo. Solo existen 4 pares; si pide count="5", TDC no
se pone a batallar: dice la verdad de inmediato:
tdc: uniq "P" is infeasible — only 4 distinct combinations exist, but 5 unique rows were requested.
El número máximo de combinaciones únicas está acotado por el producto de la cantidad de
valores distintos de cada campo. Cuando un campo saca valores al azar
(text sin percent), una muestra sesgada puede reducir el
conjunto aprovechable. Para uniq, deje un margen cómodo (muchas más combinaciones
posibles que el count), o fije percent para lograr un reparto parejo.
<uniq> — entre secuencias separadas
Cuando los campos viven en secuencias distintas, envuélvalos en <uniq>…</uniq>: la
combinación de los valores de esas secuencias se vuelve única en todas las filas:
<uniq>
<sequence name="First"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="Last"><gen type="template" value="person.lastName"/></sequence>
</uniq>
<block><line><data>${{First}} ${{Last}}</data></line></block>
En el grupo solo pueden ir secuencias de un valor (un <gen>
simple, un <mix> o un <switch>); una secuencia compuesta no puede.
Se trata de la unicidad de una combinación de campos, no de un contador. Para un
número corrido, use increment.
Cómo hacer único un valor unido
uniq es una propiedad del sorteo. Una secuencia cuyo valor se
computa, o se elige por fila con if=, no sale de ninguna
bolsa — no hay nada que tomar sin reposición —, así que uniq= sobre ella se rechaza
con TDC218 en vez de ignorarse en silencio.
Ponga uniq sobre las partes y únalas en la salida:
<uniq>
<sequence name="Area"><gen type="number" value="900..999"/></sequence>
<sequence name="Group"><gen type="number" value="1..99" length="2" first_zero="true"/></sequence>
<sequence name="Serial"><gen type="number" value="1..9999" length="4" first_zero="true"/></sequence>
</uniq>
<block><line><data>${{Area}}${{Group}}${{Serial}}</data></line></block>
La terna (Area, Group, Serial) es única en cada fila y, como cada parte tiene un
ancho fijo — 3, 2 y 4 dígitos —, la cadena de nueve dígitos se puede volver a
partir en la terna de una sola manera. Una terna única da, pues, una cadena única.
Esa última frase es todo el truco, y también su límite. Si el ancho de una parte
variara, dos ternas distintas se unirían en la misma cadena: 9|15… y 91|5… se leen
igual cuando el límite ya no está.
Volúmenes grandes
uniq corre en disco de forma predeterminada, sin banderas — pero ninguna de sus formas
corre en el motor rápido de streaming, y cuál de los otros dos la toma depende de cómo la
haya escrito:
uniq="true"sobre una sola columna sorteada — el caso común — extrae sin reposición, y para eso hacen falta la bolsa y los valores ya tomados. Ese es el motor en memoria: la memoria crece concounty la corrida queda limitada por la RAM.- Un
uniqcompuesto, ununiqsobre un contador o un grupo<uniq>en el nivel env van al motor exacto en disco: acomoda cada columna y luego revisa las tuplas y repara las colisiones. La memoria se mantiene acotada; el tiempo no.
Qué motor corre su configuración
tiene el enrutamiento completo, incluidas las cuatro formas sin uniq que también acaban
en memoria.
uniq exacto sobre una salida enorme es LENTO — uniq + percent más que nadaLa revisión de ordenar-y-reparar es exhaustiva, y su costo crece más rápido que linealmente con el número de filas. La memoria se mantiene acotada, pero el tiempo no — cientos de miles de filas únicas ya tardan minutos, y los millones pueden correr horas o más. Es el precio honesto de garantizar ningún repetido en un archivo enorme.
uniq junto con percent sobre las mismas columnas es el peor caso que hay:
proporciones exactas y sin repetidos a la vez es un acomodo con restricciones encima del
ordenamiento, más lento otra vez por un amplio margen. Si una corrida tarda una eternidad,
soltar el percent o el uniq suele ser lo que lo arregla.
Para unicidad a gran escala, prefiera las clases baratas por construcción — un
contador o un rango number lo
bastante amplio como para que una colisión sea prácticamente imposible — y reserve
uniq="true" sobre columnas numéricas/percent para los tamaños donde la revisión
exhaustiva valga la espera.
La salida de emergencia mode="memory" (el motor pequeño en RAM) también soporta todas
las formas de uniq — exacto, pero acotado por la RAM. Vea Salidas grandes.
Véase también
- Secuencias — las secuencias compuestas y los
campos, las estructuras sobre las que operan
uniqy<distinct>. - Determinismo y proporciones — por qué
uniqrecalcula cuando cambia elcount.