Trilhas
/
Factor
Factor
/
Exercícios
/
Livro-razão da biblioteca
Livro-razão da biblioteca

Livro-razão da biblioteca

Exercício de aprendizagem

Introdução

Às vezes você quer combinar uma sequência em um único valor; às vezes você quer ver todos os valores intermediários que a combinação produz ao longo do caminho. O Factor divide isso em duas ferramentas: reduce (em sequences) para a redução a um único valor, e a família cumulativa em math.statistics para a forma acumulada.

reduce: a redução geral

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

reduce percorre uma sequência item por item, carregando um resultado acumulado (o acumulador) e passando-o para uma quotation de dois argumentos. A quotation recebe o acumulador atual e o próximo elemento; o que ela deixar na pilha se torna o novo acumulador.

USING: math sequences ;

{ 1 2 3 4 } 0 [ + ] reduce .         ! => 10
{ 1 2 3 4 } 1 [ * ] reduce .         ! => 24

Uma semente diferente de zero e um combinador personalizado são as partes de reduce que sum e product não alcançam. Por exemplo, o maior valor de uma sequência, com um padrão caso nenhum valor o supere:

USING: math.order ;

{ 3 1 -4 5 -2 } 0 [ max ] reduce .   ! => 5
{ -3 -1 -4 }    0 [ max ] reduce .   ! => 0

A semente 0 participa da comparação: ela funciona como o resultado quando todos os elementos perdem, então uma sequência só de valores negativos ainda produz 0, e não um menor valor arbitrário.

Reduções cumulativas

Às vezes você quer todos os resultados intermediários, não apenas o final. A família cumulativa em math.statistics retorna uma sequência do mesmo comprimento que a entrada, em que cada posição é a redução sobre o prefixo que termina naquela posição:

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 }

Um padrão útil são reduções cumulativas encadeadas: a saída de uma é ela mesma uma sequência, pronta para alimentar outra. Isso torna "resumo acumulado de um resumo acumulado" expressável em duas palavras. As combinações são flexíveis: junte-as conforme o que cada passo está resumindo.

produce: o desdobramento

reduce consome uma sequência e a transforma em um valor. produce (em sequences) faz o caminho inverso: gera uma sequência a partir de uma semente, testando e avançando repetidamente:

produce ( pred quot -- seq )

Cada iteração primeiro roda pred sobre o estado atual; se ele retornar um valor verdadeiro, quot é chamado para produzir o próximo elemento e atualizar o estado. Quando pred retorna f, a iteração para e os elementos coletados são retornados.

Um exemplo clássico é a sequência de Fibonacci (cada número é a soma dos dois anteriores). O estado atual é o par (a, b). Cada passo emite b e então substitui o 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 }

O estado atual abrange dois valores, então o corpo usa tuck (em kernel), o embaralhamento de três elementos que copia o topo para baixo do segundo, para avançar o par, e 2nip (também em kernel, o análogo de dois elementos do nip) arruma tudo no final. Lendo a chamada da esquerda para a direita:

  • O predicado [ dup 100 < ] espia o topo do par (o próximo número a ser emitido) e continua enquanto ele ainda estiver abaixo do limite.
  • O corpo [ tuck + over ] avança o estado para (b, a + b) e emite b, deixando três valores na pilha: o novo par embaixo, o número emitido no topo.
  • Depois que produce para, os dois valores restantes (o par final) são descartados com 2nip, deixando apenas a sequência produzida.

produce é o dual exato de reduce: onde reduce dobra uma sequência até reduzi-la a um valor, produce desdobra um valor até chegar a uma sequência.

Instruções

Você trabalha na biblioteca, cuidando do livro-razão das contas dos clientes. Toda semana chegam duas tarefas à sua mesa:

  • Uma fila de solicitações: créditos que o cliente pede para aplicar (devoluções de livros, multas pagas) e novos débitos que o sistema registrou (multas por atraso recém-acumuladas). A conta do cliente tem proteção de crédito: um crédito grande o bastante para deixar a conta no vermelho é aplicado apenas até o valor devido, então o saldo acumulado nunca cai abaixo de zero.
  • Uma lista de transações: lançamentos já registrados na conta. Valores positivos são débitos (novas multas), valores negativos são créditos (pagamentos).

Toda semana você faz o balanço: um saldo final depois de atender as solicitações, um saldo acumulado a partir das transações e um registro do menor saldo acumulado, para sinalizar períodos em que as multas dispararam.

1. Atenda a fila de solicitações

Defina protected-balance para receber um saldo opening e um array de requests (valores com sinal), e retornar o saldo final depois de atender cada solicitação por vez. Um saque que deixaria o saldo abaixo de zero é atendido apenas até o valor disponível, de modo que o saldo acumulado nunca caia abaixo de zero.

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

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

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

2. Saldo acumulado

Defina running-balance para receber um array de transactions e retornar uma sequência do mesmo tamanho cujo i-ésimo elemento é o saldo depois das primeiras i+1 transações (em relação a um saldo inicial zero).

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

3. Menor saldo até agora

Defina least-balance-so-far para receber um array de transactions e retornar uma sequência do mesmo tamanho cujo i-ésimo elemento é o menor saldo acumulado visto até a posição i, inclusive. Esse é o registro do menor saldo acumulado, útil para identificar dias em que a conta pareceu arriscada.

{ 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. Divida pela metade até o alvo

A biblioteca está com um programa de anistia de multas: o saldo pendente de um cliente é reduzido à metade a cada período de pagamento, até cair para o limite de perdão ou abaixo dele. Defina halve-until para receber um principal e um target, e retornar a sequência de valores divididos pela metade (usando divisão inteira), começando pela primeira divisão e continuando enquanto o valor corrente ainda estiver estritamente acima de target. O último valor emitido será o primeiro que cai para target ou abaixo dele.

100 5 halve-until .
! => { 50 25 12 6 3 }

64 1 halve-until .
! => { 32 16 8 4 2 1 }

3 5 halve-until .
! => { }
Editar via GitHub O link abre em uma nova janela ou aba
Factor Exercism

Tudo pronto para começar Livro-razão da biblioteca?

Crie sua conta no Exercism para aprender e dominar Factor com 47 conceitos163 exercícios e mentoria humana de verdade, tudo de graça.