function singleDigit(num) {
let counter = 0
let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})
if(number <= 9){
console.log(number)
}else{
console.log(number)
return singleDigit(number), counter += 1
}
}
singleDigit(39)
O código acima pega um número inteiro e o reduz para um único dígito multiplicando-o por seus próprios dígitos.
O exemplo é 39.
3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.
O console registrará:
27
14
4
Como faço para acompanhar que a função recursiva foi chamada 3 vezes?
Eu tentei adicionar um contador, mas ele não atualiza. Gostaria de receber qualquer ajuda
javascript
recursion
chs242
fonte
fonte
.map(Number)
é redundante, pois o*
operador coage os valores para numerar de qualquer maneira. ;-)-57
é realmente um-50
e um-7
.. quando analisado dessa maneira, faria uma redução de-5
x-7
produzindo um número positivo35
. Ou você deseja que ele veja apenas o sinal negativo com5
e não o7
, mesmo que o também7
seja realmente negativo. 2) Como você pretende lidar com números que incluem zero? pois isso zerará automaticamente a redução. Portanto, quanto maior o número que você passar, maior será a probabilidade de zerar. A outra opção seria pular os zerosRespostas:
Essa é uma variante quase puramente acadêmica, mas você pode usar um combinador de ponto fixo modificado para essa finalidade.
Permite encurtar e melhorar um pouco sua função original:
A partir dessa variante, podemos fatorar e curry a chamada recursiva:
Esta função agora pode ser usada com um combinador de ponto fixo; especificamente, implementei um combinador Y adaptado para JavaScript (estrito) da seguinte maneira:
onde nós temos
Ynormal(singleDigitF, 123234234) == 0
.Agora vem o truque. Como fatoramos a recursão no combinador Y, podemos contar o número de recursões dentro dele:
Uma verificação rápida no nó REPL fornece:
Este combinador agora funcionará para contar o número de chamadas em qualquer função recursiva escrita no estilo de
singleDigitF
.(Observe que há duas fontes de obtenção de zero como resposta muito frequente: excesso numérico (
123345456999999999
tornar-se123345457000000000
etc.) e o fato de você quase certamente obter zero como um valor intermediário em algum lugar, quando o tamanho da entrada estiver aumentando.)fonte
Você deve adicionar um contra-argumento à sua definição de função:
fonte
counter
é descartada e definida como0
, a menos que você a carregue explicitamente em sua chamada recursiva, como Sheraff. AesingleDigit(number, ++counter)
++counter
paracounter+1
. Eles são funcionalmente equivalentes, mas o último especifica melhor a intenção, não muda (desnecessariamente) e parâmetro e não tem a possibilidade de pós-incremento acidental. Ou melhor ainda, como é uma chamada final, use um loop.A solução tradicional é passar a contagem como parâmetro para a função, conforme sugerido por outra resposta.
No entanto, há outra solução em js. Algumas outras respostas sugeridas simplesmente declarando contagem fora da função recursiva:
Claro que isso funciona. No entanto, isso torna a função não reentrante (não pode ser chamada duas vezes corretamente). Em alguns casos, você pode ignorar esse problema e simplesmente não ligar
singleDigit
duas vezes (o javascript é de thread único, por isso não é muito difícil de fazer), mas esse é um bug que está esperando para acontecer se você atualizarsingleDigit
mais tarde para ser assíncrono e também parecer feio.A solução é declarar a
counter
variável fora, mas não globalmente. Isso é possível porque o javascript possui fechamentos:Isso é semelhante à solução global, mas cada vez que você chama
singleDigit
(que agora não é uma função recursiva), ela cria uma nova instância dacounter
variável.fonte
singleDigit
função e fornece uma maneira alternativa limpa de fazer isso sem passar um argumento imo. +1recursion
agora está completamente isolado, deve ser totalmente seguro passar no contador como o último parâmetro. Não acho que seja necessário criar uma função interna. Se você não gosta da ideia de ter parâmetros para o benefício exclusivo da recursão (eu entendo que o usuário possa mexer com eles), prenda-osFunction#bind
em uma função parcialmente aplicada.the traditional solution is to pass the count as a parameter
. Esta é uma solução alternativa em um idioma que possui encerramentos. De certa forma, é mais simples de seguir, porque é apenas uma variável em vez de um número possivelmente infinito de instâncias de variáveis. De outras maneiras, conhecer esta solução ajuda quando o que você está rastreando é um objeto compartilhado (imagine construir um mapa exclusivo) ou um objeto muito grande (como uma string HTML)counter--
seria a maneira tradicional de resolver sua reivindicação de "não pode ser chamado duas vezes corretamente"Outra abordagem, já que você produz todos os números, é usar um gerador.
O último elemento é o seu número
n
reduzido a um número de dígito e, para contar quantas vezes você iterou, basta ler o comprimento da matriz.Pensamentos finais
Você pode considerar ter uma condição de retorno antecipado em sua função. Quaisquer números com um zero no que irá retornar zero.
O mesmo pode ser dito para quaisquer números feitos
1
apenas.Por fim, você não esclareceu se aceita apenas números inteiros positivos. Se você aceitar números inteiros negativos, convertê-los em seqüências de caracteres pode ser arriscado:
Solução possível:
fonte
Houve muitas respostas interessantes aqui. Eu acho que minha versão oferece uma alternativa adicional interessante.
Você faz várias coisas com a função necessária. Você reduz recursivamente para um único dígito. Você registra os valores intermediários e deseja contar as chamadas recursivas feitas. Uma maneira de lidar com tudo isso é escrever uma função pura que retornará uma estrutura de dados que contenha o resultado final, as etapas executadas e a chamada contem tudo em uma:
Você pode registrar as etapas, se desejar, ou armazená-las para processamento adicional.
Aqui está uma versão que faz isso:
Observe que rastreamos o,
steps
mas derivamos ocalls
. Embora possamos rastrear a contagem de chamadas com um parâmetro adicional, isso parece não ganhar nada. Também pulamos omap(Number)
passo - estes serão coagidos a números em qualquer caso pela multiplicação.Se você tiver preocupações sobre a
steps
exposição desse parâmetro padrão como parte da sua API, é fácil ocultá-lo usando uma função interna como esta:E, em ambos os casos, pode ser um pouco mais limpo extrair a multiplicação de dígitos para uma função auxiliar:
fonte
digitProduct
retornaráNaN
(-39 ~> ('-' * '3') * '9'
). Portanto, você pode usar um valor absoluto de n e usar-1
ou1
como o valor inicial de sua redução.{"digit":-39,"steps":[-39],"calls":0}
, desde-39 < 9
. Embora eu concorde que isso possa estar relacionado à verificação de erros: o parâmetro é um número? - é um número inteiro positivo? - etc. Acho que não vou atualizar para incluir isso. Isso captura o algoritmo, e o tratamento de erros geralmente é específico da base de código.Se você está apenas tentando contar quantas vezes ele é reduzido e não se importa especificamente com a recursão ... basta remover a recursão. O código abaixo permanece fiel à postagem original, pois não conta
num <= 9
como necessitando de redução. Portanto,singleDigit(8)
terácount = 0
esingleDigit(39)
terácount = 3
, exatamente como o OP e a resposta aceita estão demonstrando:Não é necessário processar números 9 ou menos (ie.
num <= 9
). Infelizmente, o código OP será processadonum <= 9
mesmo que não conte. O código acima não será processado nem contadonum <= 9
. Apenas passa através.Eu escolhi não usar
.reduce
porque fazer as contas reais era muito mais rápido de executar. E, para mim, mais fácil de entender.Pensando mais sobre velocidade
Eu sinto que um bom código também é rápido. Se você estiver usando esse tipo de redução (que é muito usado em numerologia), pode estar precisando usá-lo em uma grande quantidade de dados. Nesse caso, a velocidade se tornará da maior importância.
O uso de ambos
.map(Number)
econsole.log
(em cada etapa de redução) é muito longo para executar e desnecessário. A simples exclusão.map(Number)
do OP acelerou em cerca de 4,38x. A exclusãoconsole.log
acelerava tanto que era quase impossível testar adequadamente (eu não queria esperar).Portanto, semelhante à resposta do comando personalizado , não usar
.map(Number)
nemconsole.log
pressionar os resultados em uma matriz e usar.length
paracount
é muito muito mais rápido. Infelizmente para a resposta do comando personalizado , o uso de uma função de gerador é realmente muito lento (essa resposta é cerca de 2,68x mais lenta que o OP sem.map(Number)
econsole.log
)Além disso, em vez de usar
.reduce
, apenas usei a matemática real. Somente essa única mudança acelerou minha versão da função por um fator de 3,59x.Finalmente, a recursão é mais lenta, ocupa espaço na pilha, usa mais memória e tem um limite de quantas vezes pode "se repetir". Ou, nesse caso, quantas etapas de redução ele pode usar para concluir a redução completa. A distribuição de sua recursão para loops iterativos mantém tudo no mesmo lugar na pilha e não tem limite teórico sobre quantas etapas de redução ele pode usar para concluir. Portanto, essas funções aqui podem "reduzir" praticamente qualquer número inteiro, limitado apenas pelo tempo de execução e por quanto tempo uma matriz pode ser.
Tudo isso em mente ...
A função acima é extremamente rápida. É cerca de 3,13x mais rápido que o OP (sem
.map(Number)
econsole.log
) e 8,4x mais rápido que a resposta do comando personalizado . Lembre-se de que a exclusãoconsole.log
do OP impede a produção de um número a cada etapa da redução. Portanto, a necessidade aqui de empurrar esses resultados em uma matriz.PT
fonte
I feel good code is also fast.
Eu diria que a qualidade do código deve ser medida em relação a um conjunto predefinido de requisitos. Se o desempenho não for um deles, você não ganha nada substituindo o código que qualquer um pode entender pelo código "rápido". Você não acreditaria que a quantidade de código que eu vi que foi refatorada tenha desempenho a ponto de ninguém mais entender (por algum motivo, o código ideal também tende a não ser documentado;). Por fim, esteja ciente de que listas geradas com preguiça permitem consumir itens sob demanda.[...num+''].map(Number).reduce((x,y)=> {return x*y})
ou mesmo[...String(num)].reduce((x,y)=>x*y)
declarações que estou vendo na maioria das respostas aqui. Então, para mim, isso teve o benefício adicional de entender melhor o que está acontecendo a cada iteração e muito mais rápido. Sim, o código compactado (que tem seu lugar) é terrivelmente difícil de ler. Mas, nesses casos, geralmente não se preocupa conscientemente com sua legibilidade, mas apenas o resultado final para cortar e colar e seguir em frente.digit = num%10;
num /= 10;
? Ter que fazernum - x
primeiro para remover o dígito à direita antes da divisão provavelmente forçará o compilador JIT a fazer uma divisão separada daquela que ele fez para obter o restante.var
s (JS não temint
s). Portanto,n /= 10;
será convertidon
em um flutuador, se necessário.num = num/10 - x/10
pode convertê-lo em um flutuador, que é a forma longa da equação. Portanto, eu tenho que usar a versão refatoradanum = (num-x)/10;
para mantê-la em número inteiro. Não há como encontrar no JavaScript que possa fornecer o quociente e o restante de uma operação de divisão única. Além disso,digit = num%10; num /= 10;
há duas instruções separadas e, portanto, duas operações de divisão separadas. Faz um tempo desde que eu usei C, mas achei que era verdade lá também.Por que não ligar
console.count
na sua função?Editar: snippet para experimentar no seu navegador:
Estou trabalhando no Chrome 79 e Firefox 72
fonte
Você pode usar o fechamento para isso.
Simplesmente armazene
counter
no fechamento da função.Aqui está um exemplo:
fonte
singleDigitDecorator()
continuará incrementando o mesmo contador toda vez que for chamada.Aqui está uma versão do Python que usa uma função wrapper para simplificar o contador, conforme sugerido pela resposta de slebetman - eu escrevo isso apenas porque a ideia principal é muito clara nesta implementação:
fonte