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)
Le code ci-dessus prend un entier et le réduit à un seul chiffre en le multipliant par ses propres chiffres.
L'exemple est 39.
3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.
La console enregistrera:
27
14
4
Comment savoir si la fonction récursive a été appelée 3 fois?
J'ai essayé d'ajouter un compteur mais il ne se met pas à jour. J'apprécierais toute aide
javascript
recursion
chs242
la source
la source
.map(Number)
est redondant puisque l'*
opérateur contraint les valeurs à numéroter de toute façon. ;-)-57
est vraiment un-50
et un-7
.. quand on regarde de cette façon, cela ferait une réduction de-5
x-7
donnant un nombre positif35
. Ou voulez-vous qu'il ne voit que le signe négatif avec le5
et non le7
, même si le7
est également négatif. 2) Comment comptez-vous traiter les nombres qui incluent un zéro? car cela annulera automatiquement la réduction. Par conséquent, plus vous transmettez un nombre élevé, plus il est probable qu'il sera mis à zéro. L'autre option serait de sauter les zérosRéponses:
Il s'agit d'une variante presque purement académique, mais vous pouvez utiliser un combinateur à virgule fixe modifié à cet effet.
Permet de raccourcir et d'améliorer un peu votre fonction d'origine:
À partir de cette variante, nous pouvons factoriser et curry l'appel récursif:
Cette fonction peut désormais être utilisée avec un combinateur à virgule fixe; spécifiquement, j'ai implémenté un combinateur Y adapté pour (strict) JavaScript comme suit:
où nous avons
Ynormal(singleDigitF, 123234234) == 0
.Maintenant vient le tour. Puisque nous avons factorisé la récursivité vers le combinateur Y, nous pouvons compter le nombre de récursions en son sein:
Une vérification rapide dans le Node REPL donne:
Ce combinateur fonctionnera maintenant pour compter le nombre d'appels dans n'importe quelle fonction récursive écrite dans le style de
singleDigitF
.(Notez qu'il existe deux sources d'obtention de zéro comme réponse très fréquente: le débordement numérique (
123345456999999999
devenant123345457000000000
etc.) et le fait que vous obtiendrez presque sûrement zéro comme valeur intermédiaire quelque part, lorsque la taille de l'entrée augmente.)la source
Vous devez ajouter un argument de compteur à votre définition de fonction:
la source
counter
est supprimée et définie sur0
, à moins que vous ne la reportiez explicitement dans votre appel récursif comme le fait Sheraff. AesingleDigit(number, ++counter)
++counter
pourcounter+1
. Ils sont fonctionnellement équivalents, mais ce dernier spécifie mieux l'intention, ne change pas (inutilement) et ne paramètre pas, et n'a pas la possibilité de post-incrémentation accidentelle. Ou mieux encore, puisqu'il s'agit d'un appel de queue, utilisez plutôt une boucle.La solution traditionnelle consiste à passer le compte en tant que paramètre à la fonction comme suggéré par une autre réponse.
Cependant, il existe une autre solution dans js. Quelques autres réponses suggèrent simplement de déclarer le nombre en dehors de la fonction récursive:
Cela fonctionne bien sûr. Cependant, cela rend la fonction non réentrante (ne peut pas être appelée deux fois correctement). Dans certains cas, vous pouvez ignorer ce problème et assurez-vous simplement de ne pas appeler
singleDigit
deux fois (javascript est un thread unique donc ce n'est pas trop difficile à faire), mais c'est un bug qui attend de se produire si vous mettezsingleDigit
à jour plus tard pour être asynchrone et il se sent également laid.La solution consiste à déclarer la
counter
variable à l'extérieur mais pas globalement. Ceci est possible car javascript a des fermetures:Ceci est similaire à la solution globale mais chaque fois que vous appelez
singleDigit
(ce qui n'est plus une fonction récursive), cela créera une nouvelle instance de lacounter
variable.la source
singleDigit
fonction et fournit une autre façon propre de le faire sans passer un argument imo. +1recursion
est maintenant complètement isolé, il devrait être totalement sûr de passer le compteur comme dernier paramètre. Je ne pense pas que la création d'une fonction intérieure soit nécessaire. Si vous n'aimez pas l'idée d'avoir des paramètres au seul bénéfice de la récursivité (je prends bien note que l'utilisateur pourrait jouer avec ceux-ci), alors verrouillez-les avecFunction#bind
une fonction partiellement appliquée.the traditional solution is to pass the count as a parameter
. Il s'agit d'une solution alternative dans une langue qui a des fermetures. À certains égards, il est plus simple à suivre car il ne s'agit que d'une variable au lieu d'un nombre éventuellement infini d'instances de variables. À d'autres égards, connaître cette solution aide lorsque la chose que vous suivez est un objet partagé (imaginez construire une carte unique) ou un très grand objet (comme une chaîne HTML)counter--
serait la manière traditionnelle de résoudre votre réclamation de "ne peut pas être appelé deux fois correctement"Une autre approche, puisque vous produisez tous les nombres, consiste à utiliser un générateur.
Le dernier élément est votre nombre
n
réduit à un nombre à un seul chiffre et pour compter le nombre de fois que vous l'avez répété, il suffit de lire la longueur du tableau.Dernières pensées
Vous voudrez peut-être envisager d'avoir une condition de retour précoce dans votre fonction. Tous les chiffres avec un zéro dans ce sera revenir à zéro.
La même chose peut être dite pour tous les numéros composés
1
uniquement de.Enfin, vous n'avez pas précisé si vous n'acceptez que des entiers positifs. Si vous acceptez des entiers négatifs, les convertir en chaînes peut être risqué:
Solution possible:
la source
Il y a eu beaucoup de réponses intéressantes ici. Je pense que ma version offre une alternative intéressante supplémentaire.
Vous faites plusieurs choses avec votre fonction requise. Vous le réduisez récursivement à un seul chiffre. Vous enregistrez les valeurs intermédiaires et vous souhaitez un décompte des appels récursifs effectués. Une façon de gérer tout cela est d'écrire une fonction pure qui renverra une structure de données qui contient le résultat final, les étapes prises et le nombre d'appels en un seul:
Vous pouvez ensuite enregistrer les étapes si vous le souhaitez ou les stocker pour un traitement ultérieur.
Voici une version qui fait ça:
Notez que nous suivons le
steps
mais dérivons lecalls
. Bien que nous puissions suivre le nombre d'appels avec un paramètre supplémentaire, cela ne semble rien gagner. Nous sautons également l'map(Number)
étape - ceux-ci seront de toute façon contraints aux nombres par la multiplication.Si vous craignez que ce
steps
paramètre par défaut soit exposé dans le cadre de votre API, il est assez facile de le masquer en utilisant une fonction interne comme celle-ci:Et dans les deux cas, il pourrait être un peu plus propre d'extraire la multiplication des chiffres dans une fonction d'assistance:
la source
digitProduct
retourneraNaN
(-39 ~> ('-' * '3') * '9'
). Donc, vous pouvez utiliser une valeur absolue de n et utiliser-1
ou1
comme valeur initiale de votre réduction.{"digit":-39,"steps":[-39],"calls":0}
, depuis-39 < 9
. Bien que je convienne que cela pourrait faire une vérification d'erreur: le paramètre est-il un nombre? - est-ce un entier positif? - etc. Je ne pense pas que je vais mettre à jour pour inclure cela. Cela capture l'algorithme et la gestion des erreurs est souvent spécifique à sa base de code.Si vous essayez simplement de compter combien de fois il est réduit et que vous ne vous souciez pas spécifiquement de la récursivité ... vous pouvez simplement supprimer la récursivité. Le code ci-dessous reste fidèle à la poste d'origine car il ne compte pas
num <= 9
comme nécessitant une réduction. Par conséquent,singleDigit(8)
auracount = 0
, etsingleDigit(39)
auracount = 3
, tout comme le PO et la réponse acceptée démontrent:Il n'est pas nécessaire de traiter les nombres 9 ou moins (c.-à-d.
num <= 9
). Malheureusement, le code OP sera traiténum <= 9
même s'il ne le compte pas. Le code ci-dessus ne traitera ni ne comptera pasnum <= 9
du tout. Il passe juste à travers.J'ai choisi de ne pas l'utiliser,
.reduce
car l'exécution des calculs réels était beaucoup plus rapide à exécuter. Et, pour moi, plus facile à comprendre.Réflexion sur la vitesse
Je pense que le bon code est également rapide. Si vous utilisez ce type de réduction (qui est beaucoup utilisé en numérologie), vous devrez peut-être l'utiliser sur une énorme quantité de données. Dans ce cas, la vitesse deviendra de la plus haute importance.
L'utilisation des deux
.map(Number)
etconsole.log
(à chaque étape de réduction) est à la fois très très longue à exécuter et inutile. La simple suppression.map(Number)
de l'OP l'a accéléré d'environ 4,38x. La suppressionconsole.log
a tellement accéléré qu'il était presque impossible de tester correctement (je ne voulais pas l'attendre).Donc, comme pour la réponse de customcommander , ne pas utiliser
.map(Number)
niconsole.log
et pousser les résultats dans un tableau et utiliser.length
forcount
est beaucoup plus rapide. Malheureusement pour la réponse de customcommander , l'utilisation d'une fonction de générateur est vraiment très lente (cette réponse est environ 2.68x plus lente que l'OP sans.map(Number)
etconsole.log
)En outre, au lieu d'utiliser,
.reduce
je viens d'utiliser les mathématiques réelles. Ce seul changement a accéléré à lui seul ma version de la fonction par un facteur de 3,59x.Enfin, la récursivité est plus lente, elle prend de la place dans la pile, utilise plus de mémoire et a une limite au nombre de fois qu'elle peut "se reproduire". Ou, dans ce cas, combien d'étapes de réduction il peut utiliser pour terminer la réduction complète. Déployer votre récursivité en boucles itératives le maintient au même endroit sur la pile et n'a aucune limite théorique sur le nombre d'étapes de réduction qu'il peut utiliser pour terminer. Ainsi, ces fonctions peuvent ici "réduire" presque n'importe quel entier de taille, uniquement limité par le temps d'exécution et la longueur d'un tableau.
Tout cela à l'esprit ...
La fonction ci-dessus est extrêmement rapide. Il est environ 3,13 fois plus rapide que l'OP (sans
.map(Number)
etconsole.log
) et environ 8,4 fois plus rapide que la réponse de customcommander . Gardez à l'esprit que la suppressionconsole.log
de l'OP l'empêche de produire un nombre à chaque étape de réduction. D'où la nécessité ici de pousser ces résultats dans un tableau.PT
la source
I feel good code is also fast.
Je dirais que la qualité du code doit être mesurée par rapport à un ensemble d'exigences prédéfinies. Si les performances n'en font pas partie, vous ne gagnez rien en remplaçant le code que tout le monde peut comprendre par du code "rapide". Vous ne croiriez pas la quantité de code que j'ai vu qui a été refactorisée pour être performante au point que personne ne peut plus la comprendre (pour une raison quelconque, le code optimal a également tendance à être non documenté;). Enfin, sachez que les listes générées paresseux permettent de consommer des articles à la demande.[...num+''].map(Number).reduce((x,y)=> {return x*y})
ou même[...String(num)].reduce((x,y)=>x*y)
que je vois dans la plupart des réponses ici. Donc, pour moi, cela a eu l'avantage supplémentaire de mieux comprendre ce qui se passe à chaque itération et beaucoup plus rapidement. Oui, le code minifié (qui a sa place) est terriblement difficile à lire. Mais dans ces cas, on ne se soucie généralement pas de sa lisibilité, mais plutôt du résultat final à couper et coller et passer à autre chose.digit = num%10;
num /= 10;
? Devoir faire d'num - x
abord pour supprimer le chiffre de fin avant de diviser est susceptible de forcer le compilateur JIT à faire une division distincte de celui qu'il a fait pour obtenir le reste.var
s (JS n'a pas deint
s). Par conséquent,n /= 10;
convertiran
en un flottant si nécessaire.num = num/10 - x/10
pourrait le convertir en un flottant, qui est la forme longue de l'équation. Par conséquent, je dois utiliser la version refactoriséenum = (num-x)/10;
pour le garder un entier.Il n'y a aucun moyen que je puisse trouver en JavaScript qui puisse vous donner à la fois le quotient et le reste d'une opération de division unique. En outre, ildigit = num%10; num /= 10;
y a deux déclarations distinctes et donc deux opérations de division distinctes. Cela fait un moment que je n'ai pas utilisé C, mais je pensais que c'était également le cas ici.Pourquoi ne pas appeler
console.count
dans votre fonction?Edit: Extrait à essayer dans votre navigateur:
Je le fais fonctionner dans Chrome 79 et Firefox 72
la source
Vous pouvez utiliser la fermeture pour cela.
Stockez simplement
counter
dans la fermeture de la fonction.Voici un exemple:
la source
singleDigitDecorator()
continuera à incrémenter le même compteur à chaque appel.Voici une version Python qui utilise une fonction wrapper pour simplifier le compteur, comme cela a été suggéré par la réponse de slebetman - j'écris ceci uniquement parce que l'idée centrale est très claire dans cette implémentation:
la source