Às vezes queres combinar uma sequência num único valor; outras vezes queres ver todos os valores intermédios que a combinação produz pelo caminho. O Factor divide isto 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 geralreduce ( seq init quot: ( prev elt -- next ) -- result )
reduce percorre uma sequência um item de cada vez, levando consigo um resultado acumulado (o acumulador) e alimentando com ele uma quotation de dois argumentos. A quotation recebe o valor acumulado e o elemento seguinte; o que quer que deixe na pilha passa a ser 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 a que sum e product não chegam. Por exemplo, o maior valor de uma sequência, com um valor predefinido 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 na comparação: funciona como resultado quando todos os elementos perdem, pelo que uma sequência só de valores negativos produz na mesma 0, e não um valor mínimo arbitrário.
Às vezes queres todos os resultados intermédios, não apenas o final. A família cumulativa em math.statistics devolve uma sequência com o mesmo comprimento que a entrada, em que cada posição é a redução sobre o prefixo que termina nessa 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 as reduções cumulativas encadeadas: a saída de uma é ela própria uma sequência, pronta a alimentar outra. Isso torna o "resumo acumulado de um resumo acumulado" exprimível em duas palavras. As combinações são flexíveis: junta-as aos pares com base no que cada passo está a resumir.
produce: o desdobramentoreduce consome uma sequência e produz um valor. produce (em sequences) segue o caminho inverso: gera uma sequência a partir de uma semente, testando e avançando repetidamente:
produce ( pred quot -- seq )
Cada iteração começa por executar pred sobre o estado atual; se devolver um valor verdadeiro, quot é chamada para produzir o elemento seguinte e atualizar o estado. Quando pred devolve f, a iteração para e os elementos reunidos são devolvidos.
Um exemplo clássico é a sequência de Fibonacci (cada número é a soma dos dois anteriores). O estado acumulado é o par (a, b). Cada passo emite b e substitui depois 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 acumulado abrange dois valores, pelo que o corpo usa tuck (em kernel), a reorganização 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 de nip) para arrumar tudo no fim. Lendo a chamada da esquerda para a direita:
[ dup 100 < ] espreita o topo do par (o próximo número a emitir) e continua enquanto este ainda estiver abaixo do limite.[ tuck + over ] avança o estado para (b, a + b) e emite b, deixando três valores na pilha: o novo par em baixo e o número emitido no topo.produce parar, os dois valores que ficam no fim (o par final) são descartados com 2nip, deixando apenas a sequência produzida.produce é o dual exato de reduce: onde reduce reduz uma sequência a um valor, produce desdobra um valor numa sequência.
Trabalhas na biblioteca e manténs o livro de contas dos utentes. Todas as semanas, chegam à tua secretária dois tipos de trabalho:
Todas as semanas fazes o balanço: um saldo final depois de atenderes os pedidos, um saldo acumulado por dia a partir das transações e um valor mínimo acumulado que assinala os períodos em que as multas dispararam.
Define protected-balance de modo a que receba um saldo opening e um
array de requests (valores com sinal) e devolva o saldo final depois de
atender cada pedido pela ordem. Um levantamento que faria o saldo descer
abaixo de zero é atendido apenas até ao valor disponível, pelo que o saldo
acumulado nunca desce abaixo de zero.
100 { 50 -200 30 } protected-balance .
! => 30
500 { 100 -300 -250 } protected-balance .
! => 50
0 { -10 50 } protected-balance .
! => 50
Define running-balance de modo a que receba um array de transactions e
devolva uma sequência com o mesmo comprimento, cujo elemento de índice i
é o saldo após as primeiras i+1 transações (em relação a um saldo inicial
de zero).
{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }
Define least-balance-so-far de modo a que receba um array de
transactions e devolva uma sequência com o mesmo comprimento, cujo
elemento de índice i é o saldo acumulado mais baixo observado até à
posição i, inclusive. Este é o mínimo acumulado, útil para identificar os
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 }
A biblioteca tem um programa de amnistia de multas: o saldo em dívida de um
utente é reduzido para metade em cada período de pagamento, até ficar igual
ou abaixo de um limiar de perdão. Define halve-until de modo a que receba
um principal e um target e devolva a sequência de valores reduzidos para
metade (usando divisão inteira), começando na primeira redução para metade
e continuando enquanto o valor corrente for estritamente superior a
target. O último valor emitido será o primeiro que fica igual ou abaixo
de target.
100 5 halve-until .
! => { 50 25 12 6 3 }
64 1 halve-until .
! => { 32 16 8 4 2 1 }
3 5 halve-until .
! => { }
Inscreve-te no Exercism para aprenderes e dominares Factor com 47 conceitos163 exercícios, e mentoria humana real, tudo grátis.