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)
El código anterior toma un número entero y lo reduce a un solo dígito multiplicándolo por sus propios dígitos.
El ejemplo es 39.
3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.
La consola registrará:
27
14
4
¿Cómo hago un seguimiento de que la función recursiva se llamó 3 veces?
He intentado agregar un contador pero no se actualiza. Agradecería cualquier ayuda
javascript
recursion
chs242
fuente
fuente
.map(Number)
es redundante ya que el*
operador obliga a los valores a numerar de todos modos. ;-)-57
es realmente a-50
y a-7
... cuando se mira de esta manera, reduciría-5
x-7
dando un número positivo35
. ¿O desea que vea solo el signo negativo con el5
y no el7
, incluso aunque el en7
realidad también sea negativo? 2) ¿Cómo piensa tratar con números que incluyen un cero? ya que esto pondrá a cero automáticamente la reducción. Por lo tanto, cuanto mayor sea el número que ingrese, más probable será que se reduzca a cero. La otra opción sería omitir los cerosRespuestas:
Esta es una variante casi puramente académica, pero puede usar un combinador de punto fijo modificado para este propósito.
Permite acortar y mejorar un poco tu función original:
A partir de esta variante, podemos descomponer y cursar la llamada recursiva:
Esta función ahora se puede usar con un combinador de punto fijo; específicamente implementé un combinador Y adaptado para JavaScript (estricto) de la siguiente manera:
donde tenemos
Ynormal(singleDigitF, 123234234) == 0
.Ahora viene el truco. Como hemos factorizado la recursividad al combinador Y, podemos contar el número de recursiones dentro de él:
Una comprobación rápida en el Node REPL proporciona:
Este combinador ahora funcionará para contar el número de llamadas en cualquier función recursiva escrita al estilo de
singleDigitF
.(Tenga en cuenta que hay dos fuentes para obtener cero como respuesta muy frecuente: desbordamiento numérico (
123345456999999999
convertirse en123345457000000000
etc.) y el hecho de que casi seguramente obtendrá cero como valor intermedio en algún lugar, cuando el tamaño de la entrada está creciendo).fuente
Debe agregar un argumento contrario a la definición de su función:
fuente
counter
se eliminaría y establecería0
, a menos que la transfiera explícitamente en su llamada recursiva como lo hace Sheraff. AesingleDigit(number, ++counter)
++counter
acounter+1
. Son funcionalmente equivalentes, pero este último especifica la intención mejor, no muta (innecesariamente) ni parámetro, y no tiene la posibilidad de un incremento posterior accidental. O mejor aún, ya que es una llamada de cola, use un bucle en su lugar.La solución tradicional es pasar el conteo como parámetro a la función como lo sugiere otra respuesta.
Sin embargo, hay otra solución en js. Algunas otras respuestas sugirieron simplemente declarar el recuento fuera de la función recursiva:
Esto por supuesto funciona. Sin embargo, esto hace que la función no sea reentrante (no se puede llamar dos veces correctamente). En algunos casos, puede ignorar este problema y simplemente asegurarse de no llamar
singleDigit
dos veces (javascript tiene un solo subproceso, por lo que no es demasiado difícil de hacer), pero este es un error que espera suceder si actualizasingleDigit
más tarde para ser asincrónico y también se siente feo.La solución es declarar la
counter
variable afuera pero no globalmente. Esto es posible porque JavaScript tiene cierres:Esto es similar a la solución global, pero cada vez que llame
singleDigit
(que ahora no es una función recursiva) creará una nueva instancia de lacounter
variable.fuente
singleDigit
función y proporciona una forma limpia alternativa de hacerlo sin pasar un argumento imo. +1recursion
ahora está completamente aislado, debería ser totalmente seguro pasar el contador como último parámetro. No creo que sea necesario crear una función interna. Si no le gusta la idea de tener parámetros para el único beneficio de la recursión (sí, considero que el usuario podría meterse con ellos), luego enciérrelosFunction#bind
en una función parcialmente aplicada.the traditional solution is to pass the count as a parameter
. Esta es una solución alternativa en un lenguaje que tiene cierres. De alguna manera, es más simple de seguir porque es solo una variable en lugar de un número posiblemente infinito de instancias variables. En otras formas, conocer esta solución ayuda cuando lo que está rastreando es un objeto compartido (imagine construir un mapa único) o un objeto muy grande (como una cadena HTML)counter--
sería la forma tradicional de resolver su reclamo de "no se puede llamar dos veces correctamente"Otro enfoque, ya que produce todos los números, es usar un generador.
El último elemento es su número
n
reducido a un número de un solo dígito y para contar cuántas veces ha iterado, solo lea la longitud de la matriz.Pensamientos finales
Es posible que desee considerar tener una condición de retorno temprano en su función. Cualquier número con un cero en ella se devolverá cero.
Lo mismo puede decirse de cualquier número compuesto
1
únicamente.Finalmente, no aclaró si acepta solo enteros positivos. Si acepta enteros negativos, convertirlos en cadenas puede ser arriesgado:
Solución posible:
fuente
Ha habido muchas respuestas interesantes aquí. Creo que mi versión ofrece una alternativa interesante adicional.
Haces varias cosas con tu función requerida. Lo reduce recursivamente a un solo dígito. Registra los valores intermedios y desea contar las llamadas recursivas realizadas. Una forma de manejar todo esto es escribir una función pura que devolverá una estructura de datos que contenga el resultado final, los pasos tomados y el recuento de llamadas, todo en uno:
Luego puede registrar los pasos si lo desea, o almacenarlos para su posterior procesamiento.
Aquí hay una versión que hace eso:
Tenga en cuenta que rastreamos el
steps
pero derivamos elcalls
. Si bien podríamos rastrear el conteo de llamadas con un parámetro adicional, eso parece no ganar nada. También nos saltamos elmap(Number)
paso: estos se convertirán en números en cualquier caso mediante la multiplicación.Si le preocupa que ese
steps
parámetro predeterminado se exponga como parte de su API, es bastante fácil ocultarlo utilizando una función interna como esta:Y en cualquier caso, podría ser un poco más limpio extraer la multiplicación de dígitos en una función auxiliar:
fuente
digitProduct
devolveráNaN
(-39 ~> ('-' * '3') * '9'
). Por lo tanto, es posible que desee utilizar un valor absoluto de ny usar-1
o1
como el valor inicial de su reducción.{"digit":-39,"steps":[-39],"calls":0}
desde entonces-39 < 9
. Si bien estoy de acuerdo en que esto podría funcionar con alguna comprobación de errores: ¿ es el parámetro un número? - ¿Es un número entero positivo? - etc. No creo que actualice para incluir eso. Esto captura el algoritmo, y el manejo de errores a menudo es específico de la base de código de uno.Si solo está tratando de contar cuántas veces se reduce y no le importa la recursión específicamente ... simplemente puede eliminarla. El siguiente código permanece fiel a la publicación original, ya que no cuenta
num <= 9
como una necesidad de reducción. Por lo tanto,singleDigit(8)
tendrácount = 0
ysingleDigit(39)
tendrácount = 3
, al igual que el OP y la respuesta aceptada demuestran:No es necesario procesar los números 9 o menos (es decir
num <= 9
). Lamentablemente, el código OP se procesaránum <= 9
aunque no lo cuente. El código anterior no procesará ni contaránum <= 9
en absoluto. Simplemente lo pasa a través.Elijo no usarlo
.reduce
porque hacer las matemáticas reales fue mucho más rápido de ejecutar. Y, para mí, más fácil de entender.Más pensamiento sobre la velocidad
Siento que un buen código también es rápido. Si está utilizando este tipo de reducción (que se usa mucho en numerología), es posible que necesite usarlo en una gran cantidad de datos. En este caso, la velocidad se convertirá en la más importante.
Usar ambos
.map(Number)
yconsole.log
(en cada paso de reducción) son muy largos de ejecutar e innecesarios. Simplemente eliminar.map(Number)
del OP lo aceleró en aproximadamente 4.38x. La eliminación loconsole.log
aceleró tanto que era casi imposible probarlo adecuadamente (no quería esperar).Entonces, de manera similar a la respuesta de customcommander , no usar
.map(Number)
niconsole.log
empujar los resultados a una matriz y usar.length
forcount
es mucho más rápido. Desafortunadamente para la respuesta de customcommander , el uso de una función de generador es realmente muy lento (esa respuesta es aproximadamente 2.68 veces más lenta que el OP sin.map(Number)
yconsole.log
)Además, en lugar de usar
.reduce
, solo utilicé las matemáticas reales. Este solo cambio aceleró mi versión de la función en un factor de 3.59x.Finalmente, la recursividad es más lenta, ocupa espacio en la pila, usa más memoria y tiene un límite de cuántas veces puede "repetirse". O, en este caso, cuántos pasos de reducción puede usar para finalizar la reducción completa. El despliegue de su recursión a bucles iterativos lo mantiene todo en el mismo lugar en la pila y no tiene un límite teórico sobre cuántos pasos de reducción puede usar para terminar. Por lo tanto, estas funciones aquí pueden "reducir" casi cualquier número entero de cualquier tamaño, solo limitado por el tiempo de ejecución y el tiempo que puede durar una matriz.
Todo esto en mente ...
La función anterior se ejecuta extremadamente rápido. Es aproximadamente 3.13 veces más rápido que el OP (sin
.map(Number)
yconsole.log
) y aproximadamente 8.4 veces más rápido que la respuesta de customcommander . Tenga en cuenta que eliminarconsole.log
del OP evita que produzca un número en cada paso de reducción. Por lo tanto, la necesidad aquí de llevar estos resultados a una matriz.PT
fuente
I feel good code is also fast.
Diría que la calidad del código tiene que medirse con un conjunto predefinido de requisitos. Si el rendimiento no es uno de ellos, entonces no gana nada al reemplazar el código que cualquiera puede entender con código "rápido". No creería que la cantidad de código que he visto que se ha refactorizado es performante hasta el punto que ya nadie puede entenderlo (por alguna razón, el código óptimo también tiende a ser indocumentado). Finalmente, tenga en cuenta que las listas generadas de forma diferida le permiten a uno consumir artículos a pedido.[...num+''].map(Number).reduce((x,y)=> {return x*y})
o incluso las[...String(num)].reduce((x,y)=>x*y)
declaraciones que estoy viendo en la mayoría de las respuestas aquí. Entonces, para mí, esto tuvo el beneficio adicional de una mejor comprensión de lo que sucede en cada iteración y mucho más rápido. Sí, el código minificado (que tiene su lugar) es terriblemente difícil de leer. Pero en esos casos, en general, a uno no le importa conscientemente su legibilidad, sino simplemente el resultado final para cortar, pegar y seguir adelante.digit = num%10;
num /= 10;
? Tener que hacernum - x
primero para eliminar el dígito final antes de dividir es probable que obligue al compilador JIT a hacer una división separada de la que hizo para obtener el resto.var
s (JS no tieneint
s). Por lo tanto,n /= 10;
se convertirán
en un flotador si es necesario.num = num/10 - x/10
podría convertirlo en un flotador, que es la forma larga de la ecuación. Por lo tanto, tengo que usar la versión refactorizadanum = (num-x)/10;
para mantenerlo como un número entero. No hay forma de que pueda encontrar en JavaScript que pueda brindarle el cociente y el resto de una operación de división única. Además,digit = num%10; num /= 10;
hay dos declaraciones separadas y, por lo tanto, dos operaciones de división separadas. Ha pasado un tiempo desde que usé C, pero también pensé que era cierto allí.¿Por qué no hacer una llamada
console.count
en su función?Editar: Fragmento para probar en su navegador:
Lo tengo trabajando en Chrome 79 y Firefox 72
fuente
Puedes usar el cierre para esto.
Simplemente almacene
counter
en el cierre de la función.Aquí hay un ejemplo:
fuente
singleDigitDecorator()
seguirá incrementando el mismo contador cada vez que se llame.Aquí hay una versión de Python que usa una función de contenedor para simplificar el contador, como ha sido sugerido por la respuesta de slebetman: escribo esto solo porque la idea central es muy clara en esta implementación:
fuente