Varios valores en una celda (repeat)
Conviene usarlo cuando un campo no siempre tiene un solo valor. Un pedido lleva un
artículo o cinco. Un artículo carga una sola etiqueta o cuatro. Un sensor reporta tres
lecturas a la vez. repeat hace que un mismo <gen> emita
varios valores dentro de una celda en lugar de uno — y la cantidad puede variar de
fila en fila.
Antes de repeat, la única manera de fingir un campo de longitud variable eran cinco
secuencias separadas con la mitad de las filas en blanco. Ahora es un atributo en el
generador.
Las salidas de ejemplo de abajo son ilustrativas — los valores exactos que produce un
seed dado pueden cambiar entre versiones del core, pero la estructura que garantiza
cada forma (cuántos valores caen en una celda, y cómo se unen) no cambia.
- Auna celda por fila, con entre uno y cuatro valores
- Bcuántas filas salieron con cada longitud — los conteos son una cuota exacta, no una aproximación
De un vistazo
| Atributo | Qué significa |
|---|---|
repeat="3" | exactamente tres valores |
repeat="1..5" | entre uno y cinco — cada longitud recibe una parte exacta de las filas |
repeat="0..3" | se permite el cero — la celda puede salir vacía |
separator=" " | con qué unirlos; por omisión es una coma |
El límite superior es 64. separator sin repeat es un error — no hay nada que unir.
repeat="1..4" no tira un dado por fila. TDC le da a cada longitud una parte exacta
de la corrida, igual que percent: 200 filas salen
50 / 50 / 50 / 50 y 201 filas salen 51 / 50 / 50 / 50. Eso es lo que mantiene exactas
también las proporciones de los valores (vea «Listas de palabras y proporciones exactas»
más abajo).
El precio es el mismo que paga todo diseño que abarca la corrida entera: cambie
count y las longitudes se reparten de nuevo, así que una corrida corta no es el
comienzo de una larga. Vea
Determinismo y proporciones.
repeat="1..5" — una cantidad que varía por fila
Este es el caso de todos los días. Una línea de pedido debería llevar la cantidad de artículos que el pedido haya tenido. Aquí cada canasta saca de uno a cuatro SKU, unidos por espacios:
<env count="8" seed="basket-7">
<sequence name="Id"><gen type="increment" value="1"/></sequence>
<sequence name="Items">
<gen type="regex" value="SKU-[0-9]{4}" repeat="1..4" separator=" "/>
</sequence>
</env>
<block>
<line><data>order ${{Id}}: ${{Items}}</data></line>
</block>
order 1: SKU-5365 SKU-2241 SKU-0758 SKU-9382 order 2: SKU-2033 SKU-3412 SKU-3799 order 3: SKU-3278 order 4: SKU-3984 SKU-4578 order 5: SKU-5351 SKU-5903 order 6: SKU-3412 order 7: SKU-0258 SKU-3326 SKU-1157 order 8: SKU-3205 SKU-4821 SKU-3618 SKU-2450
El tamaño de la canasta se mueve entre uno y cuatro artículos — como en los pedidos
reales. Por qué: cualquier campo cuya cardinalidad en el mundo real varíe (artículos
de un pedido, etiquetas, números telefónicos, direcciones) se traduce directo a un rango
min..max.
repeat="3" — una cantidad fija de valores
Dé un solo entero y cada celda llevará exactamente esa cantidad de valores. Útil para tokens de forma fija — un código de tres grupos, una terna (x, y, z), un conjunto de tres lecturas:
<sequence name="Code">
<gen type="regex" value="[A-Z]{2}" repeat="3" separator="-"/>
</sequence>
QR-LM-ZP BX-TT-KD WM-AE-RH NP-CC-JU FL-GO-VS DK-HY-QN
Por qué: cuando la forma es fija y solo varían los valores, repeat="3" es más claro
que tres campos separados pegados en el bloque de salida.
repeat="0..3" — se permite el cero
Un rango que empieza en 0 deja que una celda salga vacía. Eso es justo lo correcto
para una lista opcional — algunos artículos no tienen ninguna etiqueta:
<sequence name="Tags">
<gen type="text" value="news,tech,sport,food,travel" repeat="0..2" separator=", "/>
</sequence>
article 1: tech, news article 2: article 3: sport article 4: article 5: travel, food article 6: news
Los artículos 2 y 4 sacaron longitud cero, así que su celda queda en blanco — no es un
valor especial, solo una lista vacía. Por qué: «a veces presente, a veces ausente» es
una forma real de los datos, y 0..n la modela sin una segunda secuencia.
separator= — cómo se unen los valores
Sin separator, los valores se unen con una coma. Póngale cualquier cadena para que
coincida con el formato que está construyendo — un espacio, una barra vertical, " | ",
"; ", lo que necesite la columna.
<sequence name="Letters">
<gen type="text" value="a,b,c,d,e" repeat="3"/> <!-- coma por omisión -->
</sequence>
<sequence name="Piped">
<gen type="text" value="a,b,c,d,e" repeat="3" separator=" | "/>
</sequence>
d,a,c d | a | c b,e,b b | e | b a,c,e a | c | e
Por qué: la celda unida es apenas texto, así que el separador es la manera de hacerla encajar en una columna de CSV, en una lista en línea estilo JSON o en una línea legible para una persona.
accumulate= — un total acumulado a lo largo de la lista
Muchas veces los valores de una celda son pasos de una misma cosa, no tres sorteos
independientes: las líneas de un ticket, los tramos de un viaje, los minutos de una
sesión. accumulate= reemplaza la lista por su total acumulado.
<gen type="number" value="150..900" decimals="2" repeat="3" separator=", " accumulate="sum"/>
792.47, 325.07, 563.18 → 459.93, 1277.62, 1909.65 814.04, 304.59, 456.76 → 495.46, 984.22, 1420.44 471.89, 479.67, 795.74 → 718.26, 1309.23, 1948.80
A la izquierda, el mismo generador sin accumulate=; a la derecha, con él. El último
elemento es el total, y cada uno antes de él es el subtotal en ese paso.
accumulate= | Cada elemento pasa a ser |
|---|---|
sum | la suma de todo lo anterior, incluido él |
max | el mayor visto hasta ahí — un récord parcial |
min | el menor visto hasta ahí |
pico 22,22,22,83,83 pico 11,48,54,54,54 pico 57,60,62,62,93
La aritmética es exacta, también con centavos. La suma se hace sobre enteros
escalados por la fracción más ancha de la lista, nunca en punto flotante — por eso
19.99 + 0.01 da 20.00 y no 20.000000000000004, y da el mismo 20.00 en las cinco
implementaciones.
min y max devuelven un elemento que ya existe, así que conservan su escritura. Un
valor sorteado como 007 sigue siendo 007, no se convierte en 7.
Un elemento vacío se saltea. missing= deja celdas en
blanco, y una vacía deja el acumulador quieto en vez de contar como cero: «ese día no se
tomó la lectura» no debería poner el contador en cero.
accumulate= necesita una lista, o sea repeat= (TDC237), y la operación tiene que ser
una de esas tres (TDC238).
Acá la acumulación ocurre dentro de un registro. Para un total que se arrastra de fila
en fila —el saldo de una cuenta, un contador que solo sube— la construcción es otra:
<gen type="running">.
Los valores faltantes, las anomalías y el formato actúan por elemento
Esto es lo único que hay que recordar: como repeat vive en el generador, todo lo que
ese generador hace le pasa a cada valor por separado, no a la celda como un todo. Los
huecos de datos faltantes, la inyección de anomalías y el
formato de salida operan todos elemento por elemento.
La ventaja práctica se ve con una columna de banderas. Cuando un generador marca anomalías, su campo de bandera se vuelve una lista de la misma longitud que las lecturas — una bandera por lectura, alineadas exactamente:
5,500,100 -> false,true,true 800,200,1 -> true,true,false 6,8,7 -> false,false,false 100,300,900 -> true,true,true
La bandera queda precisamente donde está el valor atípico. Se puede probar un detector no al nivel grueso de «algo en este lote andaba mal», sino exactamente — qué lectura se rompió.
Si en cambio lo que quiere es «el registro entero está corrupto», ese es otro trabajo para
otra herramienta: una rama de <mix> con un flag a nivel de registro, no repeat.
Listas de palabras y proporciones exactas
La lista más común es un conjunto de etiquetas, y repeat funciona directo sobre el
generador text:
<sequence name="Tags">
<gen type="text" value="news,tech,sport,food,travel" repeat="1..3" separator=", "/>
</sequence>
article 1: [food] article 2: [tech, news] article 3: [news, tech, travel] article 4: [sport, tech] article 5: [travel, travel, news] article 6: [sport]
Aquí viene la parte sutil. text acomoda sus valores en
proporciones exactas mediante una máscara percent, no
aproximadas — y esa garantía sobrevive a repeat, tanto con una cantidad fija como con
un rango.
Funciona porque TDC decide primero todas las longitudes (también por una cuota exacta) y solo después llena las ranuras resultantes. Como las longitudes se conocen de antemano, también se conoce la cantidad total de ranuras — no se genera nada en vano y no se pierde ninguna cuota.
Comprobado sobre 120 000 filas con repeat="1..4" y percent="40,30,20,10":
row lengths: 30000 x1 30000 x2 30000 x3 30000 x4 (exactamente un cuarto cada una) values: 120000 news 90000 tech 60000 sport 30000 food (exactamente 40/30/20/10)
300 000 ranuras en total, y la distribución cae exacta. Ejecútelo con --jobs 7 y obtiene
los mismos números — el archivo es idéntico byte por byte.
El [travel, travel, news] de arriba está bien, no es un error. Cada ranura se llena de
forma independiente, así que el mismo valor puede caer dos veces — dos lecturas de 40 o
dos veces el mismo artículo en un carrito son datos normales. No hay nada integrado del
tipo «hazlos distintos» para repeat; si necesita eso, es otra restricción distinta.
Dónde repeat no funciona
Unos cuantos generadores atan cada valor al número de fila, y repeat no se les puede
aplicar:
| Generador | Por qué no |
|---|---|
increment, decrement | el valor depende de la posición de la fila |
timeseries, pattern | lo mismo — el valor está atado a la posición |
Para estos, el índice de un elemento dependería de qué tan largas resultaron todas las
filas anteriores — una cantidad aleatoria. Eso haría imposible calcular una fila sin sus
vecinas, rompiendo el streaming y --jobs. TDC se niega con un error claro (TDC204) en
vez de hacer lo incorrecto en silencio.
Qué más no acepta TDC
| Lo que escribió | Lo que dice |
|---|---|
repeat="many", repeat="1.5" | TDC195 — se requiere un entero |
repeat="-1", repeat="5..2" | TDC195 — el mínimo no puede ser negativo ni mayor que el máximo |
repeat="1..65" | TDC195 — el límite superior es 64 |
separator=";" sin repeat | TDC198 — no hay nada que unir |
repeat o separator en un <mix> | TDC196 — un mix elige una rama, no construye una lista |
Sobre esa última: un <mix> elige entre ramas, así que «repítelo» no tiene un
significado bien definido. Si quiere una lista dentro de una rama, ponga repeat en el
<gen> que está dentro del <case>.
A gran escala
repeat no le estorba ni al streaming ni a --jobs: una fila se sigue calculando de forma
independiente de sus vecinas. Dos cosas distintas lo hacen posible, y es fácil
confundirlas:
- La longitud de la lista de cada fila viene de la cuota sobre la corrida entera (arriba). Una fila puede deducirla de su propio número — no necesita a sus vecinas.
- Los valores se sacan luego para el máximo (para
repeat="1..5", cinco) y los sobrantes se descartan, de modo que la posición en el flujo aleatorio depende solo del número de fila y nunca de qué longitud tuvieron las filas anteriores.
Eso es lo que permite calcular una fila sin calcular las anteriores, y es la razón por la que la salida multihilo coincide byte por byte con la de un solo hilo.
De ahí sale también el tope de 64: un repeat grande es trabajo real gastado, aunque la
fila termine con solo dos elementos. Vea
Salidas grandes.
En Parquet, una lista de verdad
Cuando se exporta a Parquet, una columna con repeat se vuelve una lista real, no una
cadena unida. El tipo del elemento se infiere automáticamente:
id INT64 REQUIRED items LIST of BYTE_ARRAY (UTF8) prices LIST of INT64 city BYTE_ARRAY REQUIRED
{"id":1,"items":["bread","cheese"], "prices":[262], "city":"Boston"}
{"id":3,"items":["cheese","milk"], "prices":[469,241,188], "city":"Boston"}
{"id":6,"items":["milk","bread","coffee"], "prices":[81,262], "city":"Denver"}Una lista vacía sigue siendo una lista vacía — no desaparece. Y un valor faltante dentro de
una lista produce un null real en el lugar del elemento: en texto eso es
indistinguible de una cadena vacía, pero Parquet lo registra con exactitud.
{"t":[6,5,null]}También se puede definir el tipo a mano: type="[]int64",
type="[]decimal(18,2)". Escribir type="[]int64|null" significa «una lista cuyo
elemento puede ser null» — precisamente lo que produce un valor faltante por elemento.
Vea también
text— listas separadas por comas y las proporciones exactas depercent, querepeatconserva.- Contadores —
increment/decrement, los generadores atados a la posición a los querepeatno se les puede aplicar. - Máscaras y mayúsculas — el formato por elemento que se aplica a
cada valor de una celda con
repeat. - Salidas grandes — por qué
repeatsigue siendo seguro para streaming e idéntico entre distintos--jobs.