A veces quieres combinar una secuencia en un único valor; a veces quieres ver cada valor intermedio que produce la combinación por el camino. Factor divide esto en dos herramientas: reduce (en sequences) para la reducción a un único valor, y la familia acumulativa de math.statistics para la forma progresiva.
reduce: la reducción generalreduce ( seq init quot: ( prev elt -- next ) -- result )
reduce recorre una secuencia elemento a elemento, llevando consigo un resultado acumulado (el acumulador) y pasándolo a un quotation de dos argumentos. El quotation recibe el acumulador actual y el siguiente elemento; lo que deje en la pila se convierte en el nuevo acumulador.
USING: math sequences ;
{ 1 2 3 4 } 0 [ + ] reduce . ! => 10
{ 1 2 3 4 } 1 [ * ] reduce . ! => 24
Una semilla distinta de cero y un combinador personalizado son las partes de reduce a las que sum y product no llegan. Por ejemplo, el mayor valor de una secuencia, con un valor por defecto si ninguno lo supera:
USING: math.order ;
{ 3 1 -4 5 -2 } 0 [ max ] reduce . ! => 5
{ -3 -1 -4 } 0 [ max ] reduce . ! => 0
La semilla 0 participa en la comparación: actúa como resultado cuando todos los elementos pierden, de modo que una secuencia de valores todos negativos sigue produciendo 0 en lugar de un valor mínimo arbitrario.
A veces quieres todos los resultados intermedios, no solo el final. La familia acumulativa de math.statistics devuelve una secuencia de la misma longitud que la entrada, donde cada posición es la reducción sobre el prefijo que termina en esa posición:
cum-sum ( seq -- newseq ) ! running total
cum-product ( seq -- newseq ) ! running product
cum-min ( seq -- newseq ) ! running minimum
cum-max ( seq -- newseq ) ! running maximum
USING: math.statistics ;
{ 3 1 4 1 5 9 2 6 } cum-sum . ! => { 3 4 8 9 14 23 25 31 }
{ 1 2 3 4 } cum-product . ! => { 1 2 6 24 }
{ 3 1 4 1 5 9 2 6 } cum-min . ! => { 3 1 1 1 1 1 1 1 }
{ 3 1 4 1 5 9 2 6 } cum-max . ! => { 3 3 4 4 5 9 9 9 }
Un patrón útil son las reducciones acumulativas encadenadas: la salida de una es en sí misma una secuencia, lista para alimentar a otra. Eso hace que «resumen acumulado de un resumen acumulado» se pueda expresar en dos palabras. Las combinaciones son flexibles: empareja unas con otras según lo que resuma cada paso.
produce: el desplieguereduce consume una secuencia hasta convertirla en un valor. produce (en sequences) va en la dirección contraria: genera una secuencia a partir de una semilla probando y avanzando repetidamente:
produce ( pred quot -- seq )
En cada iteración, primero se ejecuta pred sobre el estado actual; si devuelve un valor verdadero, se llama a quot para producir el siguiente elemento y actualizar el estado. Cuando pred devuelve f, la iteración se detiene y se devuelven los elementos recogidos.
Un ejemplo clásico es la sucesión de Fibonacci (cada número es la suma de los dos anteriores). El estado en curso es el par (a, b). Cada paso emite b y luego sustituye el par por (b, a + b):
USING: kernel math sequences ;
! Fibonacci numbers strictly below 100:
0 1 [ dup 100 < ] [ tuck + over ] produce 2nip .
! => { 1 1 2 3 5 8 13 21 34 55 89 }
El estado en curso abarca dos valores, así que el cuerpo usa tuck (en kernel), la reordenación de tres elementos que copia el superior debajo del segundo, para avanzar el par, y 2nip (también en kernel, el análogo de dos elementos de nip) lo deja todo recogido al final. Leyendo la llamada de izquierda a derecha:
[ dup 100 < ] mira la parte superior del par (el siguiente número que se va a emitir) y continúa mientras siga por debajo del límite.[ tuck + over ] avanza el estado a (b, a + b) y emite b, dejando tres valores en la pila: el nuevo par debajo y el número emitido arriba.produce se detiene, los dos valores sobrantes (el par final) se descartan con 2nip, dejando solo la secuencia producida.produce es el dual exacto de reduce: donde reduce pliega una secuencia hasta un valor, produce despliega un valor hasta una secuencia.
Eres el bibliotecario y llevas el libro de cuentas de los usuarios. Cada semana llegan a tu mesa dos tipos de trabajo:
Cada semana cuadras las cuentas: un saldo final tras atender las solicitudes, un saldo acumulado por día a partir de las transacciones y un nivel mínimo acumulado para señalar las rachas en que se dispararon las multas.
Define protected-balance para que reciba un saldo opening y un array de requests (importes con signo) y devuelva el saldo final tras atender cada solicitud en orden. Un reintegro que dejaría el saldo por debajo de cero se atiende solo hasta el importe disponible, así que el saldo acumulado se queda en cero como mínimo.
100 { 50 -200 30 } protected-balance .
! => 30
500 { 100 -300 -250 } protected-balance .
! => 50
0 { -10 50 } protected-balance .
! => 50
Define running-balance para que reciba un array de transactions y devuelva una secuencia de la misma longitud cuyo elemento i sea el saldo tras las primeras i+1 transacciones (partiendo de un saldo inicial de cero).
{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }
Define least-balance-so-far para que reciba un array de transactions y devuelva una secuencia de la misma longitud cuyo elemento i sea el saldo acumulado más bajo visto hasta la posición i inclusive. Es el nivel mínimo acumulado, útil para detectar los días en que la cuenta parecía arriesgada.
{ 50 -30 -20 100 } least-balance-so-far .
! => { 50 20 0 0 }
{ 200 -50 -100 -200 } least-balance-so-far .
! => { 200 150 50 -150 }
La biblioteca tiene en marcha un programa de amnistía de multas: el saldo pendiente de un usuario se divide por la mitad en cada periodo de pago hasta que cae a un umbral de condonación o por debajo de él. Define halve-until para que reciba un principal y un target, y devuelva la secuencia de valores divididos por la mitad (usando división entera) empezando por la primera división y continuando mientras el valor acumulado siga siendo estrictamente mayor que target. El último valor emitido será el primero que caiga a target o por debajo de él.
100 5 halve-until .
! => { 50 25 12 6 3 }
64 1 halve-until .
! => { 32 16 8 4 2 1 }
3 5 halve-until .
! => { }
Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.