Tracks
/
Factor
Factor
/
Ejercicios
/
El libro mayor de la bibliotecaria
El libro mayor de la bibliotecaria

El libro mayor de la bibliotecaria

Ejercicio de aprendizaje

Introducción

A veces quieres combinar una secuencia en un solo valor; otras veces quieres ver cada valor intermedio que la combinación produce en el camino. Factor divide esto en dos herramientas: reduce (en sequences) para el plegado a un solo valor, y la familia acumulativa de math.statistics para la forma acumulada.

reduce: el plegado general

reduce ( seq init quot: ( prev elt -- next ) -- result )

reduce recorre una secuencia un elemento a la vez, llevando consigo un resultado acumulado (el acumulador) y pasándolo a una quotation de dos argumentos. La 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 valor más grande de una secuencia, con un valor predeterminado por si ningún valor 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, así que una secuencia de valores todos negativos igual produce 0 en lugar de algún valor mínimo arbitrario.

Reducciones acumulativas

A veces quieres cada resultado intermedio, 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 a su vez 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: combínalas según lo que resuma cada paso.

produce: el despliegue

reduce consume una secuencia y la convierte en un valor. produce (en sequences) va en la otra dirección: genera una secuencia a partir de una semilla probando y avanzando repetidamente:

produce ( pred quot -- seq )

Cada iteración primero 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 recopilados.

Un ejemplo clásico es la sucesión de Fibonacci (cada número es la suma de los dos anteriores). El estado actual es el par (a, b). Cada paso emite b y luego reemplaza 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 actual abarca dos valores, así que el cuerpo usa tuck (en kernel), el barajado de tres elementos que copia el de arriba debajo del segundo, para avanzar el par, y 2nip (también en kernel, el análogo de dos elementos de nip) para dejar todo limpio al final. Si lees la llamada de izquierda a derecha:

  • El predicado [ dup 100 < ] examina la parte superior del par (el siguiente número que se va a emitir) y continúa mientras siga por debajo del límite.
  • El cuerpo [ tuck + over ] avanza el estado a (b, a + b) y emite b, dejando tres valores en la pila: el nuevo par abajo y el número emitido arriba.
  • Después de que 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.

Instrucciones

Estás a cargo de la biblioteca y llevas el libro mayor de las cuentas de los usuarios. Cada semana llegan a tu escritorio dos tipos de trabajo:

  • Una cola de solicitudes: créditos que un usuario pide aplicar (devoluciones de libros, multas pagadas) y nuevos débitos que el sistema ha registrado (multas por retraso recién acumuladas). La cuenta del usuario está protegida frente a los créditos: un crédito lo bastante grande como para dejar al usuario en números rojos se aplica solo hasta lo que se debe, así que el saldo corriente nunca baja de cero.
  • Una lista de transacciones: asientos ya registrados en la cuenta. Los montos positivos son débitos (multas nuevas) y los negativos son créditos (pagos).

Cada semana haces el balance: un saldo final después de aplicar las solicitudes, un saldo corriente por día a partir de las transacciones y un mínimo corriente que marca las rachas en las que se dispararon las multas.

1. Aplica la cola de solicitudes

Define protected-balance para que reciba un saldo opening y un array de requests (montos con signo) y devuelva el saldo final después de aplicar cada solicitud, una por una. Un retiro que dejaría el saldo por debajo de cero se aplica solo hasta el monto disponible, así que el saldo corriente nunca baja de cero.

100 { 50 -200 30 } protected-balance .
! => 30

500 { 100 -300 -250 } protected-balance .
! => 50

0 { -10 50 } protected-balance .
! => 50

2. Saldo corriente

Define running-balance para que reciba un array de transactions y devuelva una secuencia de la misma longitud cuyo elemento i sea el saldo después de las primeras i+1 transacciones (tomando como base un saldo inicial de cero).

{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }

3. El menor saldo hasta el momento

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 corriente más bajo visto hasta la posición i inclusive. Este es el mínimo corriente, útil para detectar los días en los que la cuenta parecía riesgosa.

{ 50 -30 -20 100 } least-balance-so-far .
! => { 50 20 0 0 }

{ 200 -50 -100 -200 } least-balance-so-far .
! => { 200 150 50 -150 }

4. Reduce a la mitad hasta el objetivo

La biblioteca tiene un programa de amnistía de multas: el saldo pendiente de un usuario se reduce a la mitad en cada período de pago hasta que queda en 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 reducidos a la mitad (usando división entera) empezando desde la primera reducción y continuando mientras el valor corriente siga siendo estrictamente mayor que target. El último valor emitido será el primero que quede en 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 .
! => { }
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
Factor Exercism

¿Todo listo para empezar El libro mayor de la bibliotecaria?

Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.