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)
Der obige Code nimmt eine Ganzzahl und reduziert sie auf eine einzelne Ziffer, indem sie mit ihren eigenen Ziffern multipliziert wird.
Beispiel ist 39.
3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.
Die Konsole protokolliert:
27
14
4
Wie verfolge ich, dass die rekursive Funktion dreimal aufgerufen wurde?
Ich habe versucht, einen Zähler hinzuzufügen, der jedoch nicht aktualisiert werden kann. Würde mich über jede Hilfe freuen
javascript
recursion
chs242
quelle
quelle
.map(Number)
ist redundant, da der*
Operator die Werte ohnehin zur Nummerierung zwingt. ;-)-57
wirklich a-50
und a-7
. Wenn man es so betrachtet, würde es eine Reduktion von-5
x bewirken,-7
was eine positive Zahl ergibt35
. Oder möchten Sie, dass nur das negative Vorzeichen mit dem5
und nicht das7
angezeigt7
wird , obwohl das tatsächlich auch negativ ist? 2) Wie wollen Sie mit Zahlen umgehen, die eine Null enthalten? da dies die Reduzierung automatisch auf Null setzt. Je größer die Zahl ist, die Sie eingeben, desto wahrscheinlicher ist es, dass sie auf Null gesetzt wird. Die andere Möglichkeit wäre, die Nullen zu überspringenAntworten:
Dies ist eine fast rein akademische Variante, aber Sie können zu diesem Zweck einen modifizierten Festpunktkombinator verwenden.
Verkürzen und verbessern Sie Ihre ursprüngliche Funktion ein wenig:
Aus dieser Variante können wir den rekursiven Aufruf herausrechnen und curry:
Diese Funktion kann jetzt mit einem Festkomma-Kombinator verwendet werden. Insbesondere habe ich einen Y-Kombinator implementiert, der für (striktes) JavaScript wie folgt angepasst ist:
wo wir haben
Ynormal(singleDigitF, 123234234) == 0
.Jetzt kommt der Trick. Da wir die Rekursion zum Y-Kombinator herausgerechnet haben, können wir die Anzahl der darin enthaltenen Rekursionen zählen:
Ein kurzer Check im Node REPL ergibt:
Dieser Kombinator zählt jetzt die Anzahl der Anrufe in einem beliebigen rekursiven Funktion, die im Stil von geschrieben wurde
singleDigitF
.(Beachten Sie, dass es zwei Ursachen gibt, um als sehr häufige Antwort Null zu erhalten: numerischer Überlauf (
123345456999999999
Werden123345457000000000
usw.) und die Tatsache, dass Sie mit ziemlicher Sicherheit irgendwo Null als Zwischenwert erhalten, wenn die Größe der Eingabe zunimmt.)quelle
Sie sollten Ihrer Funktionsdefinition ein Gegenargument hinzufügen:
quelle
counter
, wird sie verschrottet und auf gesetzt0
, es sei denn, Sie übertragen sie explizit in Ihren rekursiven Aufruf, wie dies Sheraff tut. AesingleDigit(number, ++counter)
++counter
zucounter+1
. Sie sind funktional gleichwertig, aber letztere spezifizieren die Absicht besser, mutieren nicht (unnötig) und parametrieren nicht und haben nicht die Möglichkeit, versehentlich nach dem Inkrementieren. Oder noch besser, da es sich um einen Tail-Call handelt, verwenden Sie stattdessen eine Schleife.Die traditionelle Lösung besteht darin, die Zählung als Parameter an die Funktion zu übergeben, wie in einer anderen Antwort vorgeschlagen.
Es gibt jedoch eine andere Lösung in js. Einige andere Antworten schlugen vor, einfach die Anzahl außerhalb der rekursiven Funktion zu deklarieren:
Das funktioniert natürlich. Dies macht die Funktion jedoch nicht wiedereintrittsfähig (kann nicht zweimal korrekt aufgerufen werden). In einigen Fällen können Sie dieses Problem ignorieren und einfach sicherstellen, dass Sie nicht
singleDigit
zweimal aufrufen (Javascript ist Single-Threaded, so dass es nicht zu schwierig ist), aber dies ist ein Fehler, der darauf wartet, wenn SiesingleDigit
später aktualisieren , um asynchron zu sein, und es fühlt sich auch so an hässlich.Die Lösung besteht darin, die
counter
Variable außerhalb, aber nicht global zu deklarieren . Dies ist möglich, weil Javascript Verschlüsse hat:Dies ähnelt der globalen Lösung, erstellt jedoch bei jedem Aufruf
singleDigit
(der jetzt keine rekursive Funktion ist) eine neue Instanz dercounter
Variablen.quelle
singleDigit
Funktion verfügbar und bietet eine alternative saubere Möglichkeit, dies zu tun, ohne ein Argument imo zu übergeben. +1recursion
es jetzt vollständig isoliert ist, sollte es absolut sicher sein, den Zähler als letzten Parameter zu übergeben. Ich denke nicht, dass es notwendig ist, eine innere Funktion zu schaffen. Wenn Ihnen die Idee, Parameter zum alleinigen Vorteil der Rekursion zu haben, nicht gefällt (ich gehe davon aus, dass der Benutzer damit herumspielen könnte), sperren Sie sieFunction#bind
in einer teilweise angewendeten Funktion.the traditional solution is to pass the count as a parameter
. Dies ist eine alternative Lösung in einer Sprache mit Abschlüssen. In mancher Hinsicht ist es einfacher zu folgen, da es sich nur um eine Variable anstelle einer möglicherweise unendlichen Anzahl von Variableninstanzen handelt. Auf andere Weise hilft es, diese Lösung zu kennen, wenn das Objekt, das Sie verfolgen, ein freigegebenes Objekt (stellen Sie sich vor, Siecounter--
wäre die traditionelle Art, Ihre Behauptung zu lösen, dass "nicht zweimal richtig aufgerufen werden kann"Ein anderer Ansatz, da Sie alle Zahlen erzeugen, ist die Verwendung eines Generators.
Das letzte Element ist Ihre
n
auf eine einstellige Zahl reduzierte Zahl. Um zu zählen, wie oft Sie iteriert haben, lesen Sie einfach die Länge des Arrays.Abschließende Gedanken
Möglicherweise möchten Sie eine frühzeitige Rückkehrbedingung in Ihrer Funktion in Betracht ziehen . Jegliche Zahlen mit einer Null in es werden Null zurück.
Das Gleiche gilt für alle Zahlen, die
1
nur aus bestehen.Schließlich haben Sie nicht geklärt, ob Sie nur positive ganze Zahlen akzeptieren. Wenn Sie negative Ganzzahlen akzeptieren, kann das Umwandeln in Zeichenfolgen riskant sein:
Mögliche Lösung:
quelle
Hier gab es viele interessante Antworten. Ich denke, meine Version bietet eine weitere interessante Alternative.
Sie machen mehrere Dinge mit Ihrer gewünschten Funktion. Sie reduzieren es rekursiv auf eine einzelne Ziffer. Sie protokollieren die Zwischenwerte und möchten, dass die rekursiven Aufrufe gezählt werden. Eine Möglichkeit, dies alles zu handhaben, besteht darin, eine reine Funktion zu schreiben, die eine Datenstruktur zurückgibt, die das Endergebnis, die durchgeführten Schritte und die Anzahl der Aufrufe in einem enthält:
Sie können die Schritte dann bei Bedarf protokollieren oder zur weiteren Verarbeitung speichern.
Hier ist eine Version, die das macht:
Beachten Sie, dass wir das verfolgen,
steps
aber das ableitencalls
. Während wir die Anzahl der Anrufe mit einem zusätzlichen Parameter verfolgen konnten, scheint dies nichts zu gewinnen. Wir überspringen auch denmap(Number)
Schritt - diese werden in jedem Fall durch Multiplikation zu Zahlen gezwungen.Wenn Sie Bedenken haben, dass dieser Standardparameter
steps
als Teil Ihrer API verfügbar gemacht wird, können Sie ihn mithilfe einer internen Funktion wie der folgenden leicht ausblenden:In beiden Fällen kann es etwas sauberer sein, die Ziffernmultiplikation in eine Hilfsfunktion zu extrahieren:
quelle
digitProduct
returnNaN
(-39 ~> ('-' * '3') * '9'
). Daher möchten Sie möglicherweise einen absoluten Wert von n verwenden und-1
oder1
als Anfangswert Ihrer Reduzierung verwenden.{"digit":-39,"steps":[-39],"calls":0}
, da-39 < 9
. Ich bin zwar damit einverstanden, dass dies mit einer Fehlerprüfung zu tun haben könnte: Ist der Parameter eine Zahl? - Ist es eine positive ganze Zahl? - usw. Ich glaube nicht, dass ich das aktualisieren werde, um das aufzunehmen. Dadurch wird der Algorithmus erfasst, und die Fehlerbehandlung ist häufig spezifisch für die eigene Codebasis.Wenn Sie nur versuchen zu zählen, wie oft es reduziert wird, und sich nicht speziell um die Rekursion kümmern , können Sie die Rekursion einfach entfernen. Der folgende Code bleibt dem Originalbeitrag treu, da er nicht
num <= 9
als reduktionsbedürftig gilt. DahersingleDigit(8)
wirdcount = 0
undsingleDigit(39)
wirdcount = 3
, genau wie das OP und die akzeptierte Antwort zeigen:Es ist nicht erforderlich, die Nummern 9 oder weniger (dh
num <= 9
) zu verarbeiten. Leider wird der OP-Code verarbeitet,num <= 9
obwohl er nicht gezählt wird. Der obige Code wird weder verarbeitet noch gezähltnum <= 9
. Es geht einfach durch.Ich entscheide mich, es nicht zu verwenden,
.reduce
da die Ausführung der eigentlichen Mathematik viel schneller war. Und für mich leichter zu verstehen.Weiteres Denken über Geschwindigkeit
Ich fühle mich gut Code ist auch schnell. Wenn Sie diese Art der Reduzierung verwenden (die in der Numerologie häufig verwendet wird), müssen Sie sie möglicherweise für eine große Datenmenge verwenden. In diesem Fall wird die Geschwindigkeit von größter Bedeutung sein.
Die Verwendung von beiden
.map(Number)
undconsole.log
(bei jedem Reduktionsschritt) ist sehr, sehr langwierig und unnötig..map(Number)
Durch einfaches Löschen aus dem OP wurde die Geschwindigkeit um das 4,38-fache erhöht. Das Löschenconsole.log
beschleunigte es so sehr, dass es fast unmöglich war, es richtig zu testen (ich wollte nicht darauf warten).Ähnlich wie bei der Antwort von customcommander ist es viel schneller ,
.map(Number)
nor nicht zu verwendenconsole.log
und die Ergebnisse in ein Array zu verschieben und.length
for zu verwendencount
. Unglücklicherweise für die Antwort von customcommander ist die Verwendung einer Generatorfunktion sehr, sehr langsam (diese Antwort ist ungefähr 2,68x langsamer als die OP ohne.map(Number)
undconsole.log
).Anstatt zu verwenden, habe
.reduce
ich auch nur die eigentliche Mathematik verwendet. Allein diese einzelne Änderung hat meine Version der Funktion um den Faktor 3,59 beschleunigt.Schließlich ist die Rekursion langsamer, beansprucht Stapelspeicher, verbraucht mehr Speicher und kann nur begrenzt "wiederkehren". Oder in diesem Fall, wie viele Reduktionsschritte verwendet werden können, um die vollständige Reduktion abzuschließen. Wenn Sie Ihre Rekursion in iterative Schleifen ausrollen, bleibt alles an derselben Stelle auf dem Stapel und es gibt keine theoretische Begrenzung für die Anzahl der Reduktionsschritte, die zum Beenden verwendet werden können. Somit können diese Funktionen hier nahezu jede beliebige Größe "reduzieren", die nur durch die Ausführungszeit und die Länge eines Arrays begrenzt ist.
All dies im Hinterkopf ...
Die obige Funktion läuft extrem schnell. Es ist ungefähr 3,13x schneller als das OP (ohne
.map(Number)
undconsole.log
) und ungefähr 8,4x schneller als die Antwort von customcommander . Beachten Sie, dass das Löschenconsole.log
aus dem OP verhindert, dass bei jedem Schritt der Reduzierung eine Zahl erzeugt wird. Daher besteht hier die Notwendigkeit, diese Ergebnisse in ein Array zu verschieben.PT
quelle
I feel good code is also fast.
Ich würde sagen , dass die Qualität des Codes hat gegen einen vordefinierten Satz von Anforderungen gemessen werden. Wenn die Leistung nicht dazu gehört, erhalten Sie nichts, wenn Sie Code, den jeder verstehen kann, durch "schnellen" Code ersetzen. Sie würden nicht glauben, dass die Menge an Code, die ich gesehen habe und die so überarbeitet wurde, dass sie so leistungsfähig ist, dass niemand sie mehr verstehen kann (aus irgendeinem Grund ist optimaler Code in der Regel auch nicht dokumentiert;). Beachten Sie schließlich, dass durch faul generierte Listen Artikel bei Bedarf konsumiert werden können.[...num+''].map(Number).reduce((x,y)=> {return x*y})
oder sogar[...String(num)].reduce((x,y)=>x*y)
Aussagen, die ich in den meisten Antworten hier sehe. Für mich hatte dies den zusätzlichen Vorteil, dass ich besser verstehe, was bei jeder Iteration vor sich geht, und zwar viel schneller. Ja, minimierter Code (der seinen Platz hat) ist furchtbar schwer zu lesen. In diesen Fällen kümmert man sich jedoch im Allgemeinen nicht bewusst um die Lesbarkeit, sondern nur um das Endergebnis zum Ausschneiden, Einfügen und Weitergehen.digit = num%10;
num /= 10;
ausführen können ? Wenn Sienum - x
zuerst die nachfolgende Ziffer entfernen müssen, bevor Sie sie teilen, wird der JIT-Compiler wahrscheinlich gezwungen, eine andere Division durchzuführen als die, um den Rest zu erhalten.var
s (JS hat keineint
s). Dahern /= 10;
wirdn
bei Bedarf in einen Float konvertiert .num = num/10 - x/10
könnte es in einen Float umwandeln, was die lange Form der Gleichung ist. Daher muss ich dienum = (num-x)/10;
überarbeitete Version verwenden , um eine Ganzzahl beizubehalten. In JavaScript gibt es keine Möglichkeit, die Ihnen den Quotienten und den Rest einer einzelnen Divisionsoperation liefert. Esdigit = num%10; num /= 10;
gibt auch zwei separate Anweisungen und damit zwei separate Divisionsoperationen. Es ist schon eine Weile her, seit ich C verwendet habe, aber ich dachte, dass es auch dort wahr ist.Warum rufen Sie nicht
console.count
in Ihrer Funktion an?Bearbeiten: Snippet zum Ausprobieren in Ihrem Browser:
Ich habe es in Chrome 79 und Firefox 72 funktionieren
quelle
Sie können hierfür den Verschluss verwenden.
Einfach
counter
in den Funktionsschluss speichern .Hier ist ein Beispiel:
quelle
singleDigitDecorator()
erhöht bei jedem Aufruf denselben Zähler.Hier ist eine Python-Version, die eine Wrapper-Funktion verwendet, um den Zähler zu vereinfachen, wie in der Antwort von slebetman vorgeschlagen wurde. Ich schreibe dies nur, weil die Kernidee in dieser Implementierung sehr klar ist:
quelle