Saltar al contenido principal

Acotar con filter

Sin filter, una fila sortea de todo el pool. Con él, sortea solo de los miembros que la expresión acepta.

El caso evidente: un paciente de la clínica norte tiene que ver a un médico que trabaje allí.

<tdc>
<env count="10" seed="clinic" local="en">
<pool name="Doctors" count="6">
<sequence name="clinic"><gen type="text" value="North,South"/></sequence>
<sequence name="name"><gen type="template" value="person.lastName"/></sequence>
</pool>

<sequence name="Clinic"><gen type="text" value="North,South" percent="50,50"/></sequence>
<sequence name="Patient"><gen type="template" value="person.female.firstName"/></sequence>
<sequence name="Seen"><gen type="pool" value="Doctors" filter="clinic == Clinic"/></sequence>
</env>
<block>
<line><data>${{Clinic}} | ${{Patient}} -> Dr. ${{Seen.name}} (${{Seen.clinic}})</data></line>
</block>
</tdc>
./run clinic.tdc
South | Barbara -> Dr. Smith (South)
North | Mary -> Dr. Jones (North)
South | Dorothy -> Dr. Garcia (South)
South | Jennifer -> Dr. Johnson (South)
North | Elizabeth -> Dr. Jones (North)
North | Patricia -> Dr. Jones (North)
North | Susan -> Dr. Williams (North)
South | Sarah -> Dr. Smith (South)
South | Margaret -> Dr. Garcia (South)
North | Linda -> Dr. Jones (North)

La columna de la clínica y la clínica del médico coinciden en todas las filas.

El sorteo sigue siendo uniforme

filter decide qué miembros están disponibles, no cuál se toma. Entre los que pasan, la elección es uniforme: a un paciente del norte puede tocarle cualquiera de los médicos del norte.

Vale la pena decirlo, porque la alternativa obvia — «usar el primer miembro que encaje» — le daría a todos los pacientes del norte el mismo médico y destruiría en silencio la dispersión para la que se construyó el pool.

Qué significa un nombre dentro de filter

La expresión se evalúa en dos ámbitos a la vez: los campos del miembro candidato y las columnas de la fila actual.

El nombreQué lee
clinicun campo del miembro candidato, si el pool tiene uno con ese nombre
Clinicuna columna de la fila actual
Doctors.clinicsiempre el campo del candidato — la forma cualificada
Northuna palabra suelta, leída como cadena literal

El orden importa: un nombre suelto se busca primero como campo del miembro y solo después como columna de la fila. Un nombre que es las dos cosas se rechaza en lugar de adivinarse:

./run clinic.tdc
error[TDC232]: "clinic" in filter= is both a field of pool "Doctors" and a sequence — which one is meant is not decidable
--> clinic.tdc:8:27
|
8 |     <sequence name="Seen"><gen type="pool" value="Doctors" filter="clinic == clinic"/></sequence>
|                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: Rename one of them. Qualifying one side ("Doctors.clinic") does not help: the other "clinic" still reads as the member's field, so the test would compare a value with itself.

Un nombre cualificado que el pool no tiene también se detecta:

./run clinic.tdc
error[TDC226]: filter= reads "Doctors.branch", but pool "Doctors" has no field "branch"
--> clinic.tdc:7:27
|
7 |     <sequence name="Seen"><gen type="pool" value="Doctors" filter="Doctors.branch == Site"/></sequence>
|                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: Fields of "Doctors": clinic.

Un nombre desconocido sin cualificar se deja pasar, a propósito: el lenguaje de expresiones lee una palabra suelta como cadena literal, y así es como filter="clinic == North" dice «solo los médicos del norte» sin declarar nada.

Es una expresión completa

campo == Columna es la forma habitual, pero filter admite todo lo que entiende el lenguaje de expresiones: !=, <, >, <=, >=, &&, ||, ! y aritmética.

Eso abre los casos que valen más que el de la clínica — un cliente que compra lo que puede pagar:

<tdc>
<env count="8" seed="shop" local="en">
<pool name="Catalog" count="6">
<sequence name="item" uniq="true"><gen type="text" value="Kettle,Lamp,Chair,Desk,Rug,Clock"/></sequence>
<sequence name="price"><gen type="number" value="10..300"/></sequence>
</pool>

<sequence name="Budget"><gen type="number" value="50..250"/></sequence>
<sequence name="Buys"><gen type="pool" value="Catalog" filter="price <= Budget"/></sequence>
</env>
<block>
<line><data>budget ${{Budget}} -> ${{Buys.item}} at ${{Buys.price}}</data></line>
</block>
</tdc>
./run shop.tdc
budget 232 -> Clock at 11
budget 124 -> Desk at 92
budget 61 -> Clock at 11
budget 148 -> Clock at 11
budget 208 -> Rug at 198
budget 54 -> Clock at 11
budget 102 -> Desk at 92
budget 60 -> Clock at 11

Nadie compra por encima de su presupuesto, y no hubo que enumerar nada a mano.

Escriba <= y && tal cual

TDC no expande entidades XML. filter="price &lt;= Budget" llega al analizador como esos nueve caracteres y falla. Escriba el operador que quiere decir.

Lo que cuesta

Hay dos caminos, y cuál se recorre lo decide cómo está escrito el filtro:

El filtroCómo se responde una fila
campo == Columna (en cualquier orden)el pool se agrupa por ese campo una vez; una fila cuesta una búsqueda
cualquier otra cosalos candidatos se recorren, por fila — lineal en el tamaño del pool

Los dos son correctos. La diferencia es la razón de que un pool tenga un techo de tamaño: recorrer un millón de miembros, dos mil veces, es un coste real, y el techo es donde la herramienta lo dice.

filter no es if

Los dos acotan algo, y los dos pueden aparecer en el mismo <gen>. Se diferencian en lo que sale:

Qué preguntaCuando dice que no
ifpor la fila — una respuesta por filano se genera nada; la celda queda vacía
filterpor cada candidato — una respuesta por miembrose sustituye un miembro que encaja; la celda nunca queda vacía

Así, if="Age >= 18" deja a los menores sin médico, y filter="clinic == Clinic" da médico a todos, pero de su clínica. Juntos se leen como «solo adultos, y de su propia clínica»:

<gen type="pool" value="Doctors" if="Age >= 18" filter="clinic == Clinic"/>

También preguntan por cosas distintas, y por eso un solo atributo no podía hacer los dos trabajos. if pregunta una vez por fila. filter pregunta una vez por candidato: treinta preguntas por fila para un pool de treinta.

Cuando no encaja nadie

Como filter nunca produce una celda vacía, «no encajó nadie» es un error y no un hueco. El mensaje nombra la fila y el valor que la acotó hasta cero:

./run clinic.tdc
tdcv2: pool "Doctors": no member satisfies filter="clinic == Clinic" for row 3 (Clinic="South"). A filter narrows the members a row may draw from; when it narrows them to none there is nothing to substitute. Add a member that matches, or widen the filter.

Es un rechazo en tiempo de ejecución, no un error de validación, y no puede ser de otra manera: el validador no puede saber que ningún miembro saldrá South hasta que el pool se haya sorteado. Las dos soluciones están en el mensaje — añadir un miembro que encaje o ampliar el filtro — y hay una tercera que conviene conocer: darle al campo del pool la misma lista finita de la que sortea la columna de la fila, para que todos los valores estén representados.

Relacionado

  • Resumen — qué es un pool, y el techo de tamaño al que se refiere esta página
  • Enlazar pools entre sífilter leyendo un campo de otra referencia a un pool, que es como se construye una cadena
  • Condicionesif completo, incluidos los operadores que comparte con filter