Verfolgen Sie, wie oft eine rekursive Funktion aufgerufen wurde

62

 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

chs242
quelle
4
.map(Number)ist redundant, da der *Operator die Werte ohnehin zur Nummerierung zwingt. ;-)
RobG
4
Ein paar Fragen: 1) Wie wollen Sie mit negativen Zahlen umgehen? Zum Beispiel ist die Zahl -57wirklich a -50und a -7. Wenn man es so betrachtet, würde es eine Reduktion von -5x bewirken, -7was eine positive Zahl ergibt 35. Oder möchten Sie, dass nur das negative Vorzeichen mit dem 5und nicht das 7angezeigt 7wird , 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 überspringen
Pimp Trizkit
3
Mir ist klar, dass es bei meinen obigen Fragen nicht darum geht, die Rekursion zu zählen, sondern nur um den Aspekt der Rätsellösung des in dieser Frage verwendeten Inhalts. Bitte verzeih mir.
Pimp Trizkit
3
Ich bin geschmeichelt, dass Ihnen meine Antwort gefällt, aber aus praktischen Gründen denke ich, dass stackoverflow.com/a/59570894/1346276 die sauberste allgemeine Variante ist.
Phipsgabler
2
@phipsgabler Jeder, der sich die Zeit nimmt, eine intelligente und kohärente Antwort zu schreiben, verdient ein "Gefällt mir". Vielen Dank
chs242

Antworten:

25

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:

function singleDigit(n) {
    let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
    return digitProduct <= 9 ? digitProduct : singleDigit(digitProduct);
}

// singleDigit(123234234) == 0

Aus dieser Variante können wir den rekursiven Aufruf herausrechnen und curry:

function singleDigitF(recur) {
    return function (n) {
        let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
        return digitProduct <= 9 ? digitProduct : recur()(digitProduct);
    };
}

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:

function Ynormal(f, ...args) {
    let Y = (g) => g(() => Y(g));
    return Y(f)(...args);
}

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:

function Ycount(f, ...args) {
    let count = 1;
    let Y = (g) => g(() => {count += 1; return Y(g);});
    return [Y(f)(...args), count];
}

Ein kurzer Check im Node REPL ergibt:

> Ycount(singleDigitF, 123234234)
[ 0, 3 ]
> let digitProduct = (n) => [...(n + '')].reduce((x, y) => x * y, 1)
undefined
> digitProduct(123234234)
3456
> digitProduct(3456)
360
> digitProduct(360)
0
> Ycount(singleDigitF, 39)
[ 4, 3 ]

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 ( 123345456999999999Werden 123345457000000000usw.) und die Tatsache, dass Sie mit ziemlicher Sicherheit irgendwo Null als Zwischenwert erhalten, wenn die Größe der Eingabe zunimmt.)

phipsgabler
quelle
6
An die Downvoter: Ich stimme Ihnen wirklich zu, dass dies nicht die beste praktische Lösung ist - deshalb habe ich sie als "rein akademisch" vorangestellt.
Phipsgabler
Ehrlich gesagt ist es eine großartige Lösung und völlig geeignet für den Regressions- / Mathe-Typ der ursprünglichen Frage.
Sheraff
73

Sie sollten Ihrer Funktionsdefinition ein Gegenargument hinzufügen:

function singleDigit(num, counter = 0) {
    console.log(`called ${counter} times`)
    //...
    return singleDigit(number, counter+1)
}
singleDigit(39)
Sheraff
quelle
6
genial. Es sieht so aus, als ob mein Zähler nicht funktioniert hat, weil ich ihn in der Funktion deklariert habe
chs242
7
Die Bereichsregeln von @ chs242 würden vorschreiben, dass durch das Deklarieren in der Funktion bei jedem Aufruf eine neue erstellt wird. stackoverflow.com/questions/500431/…
Taplar
10
@ chs242 es ist nicht so, dass du es innerhalb der Funktion deklariert hast. Technisch gesehen sind das auch alle Standardparameter - in Ihrem Fall ist es einfach so, dass der Wert nie auf das nächste Mal übertragen wird, wenn die Funktion rekursiv aufgerufen wird. ae Jedes Mal , wenn die Funktion ausgeführt wird counter, wird sie verschrottet und auf gesetzt 0, es sei denn, Sie übertragen sie explizit in Ihren rekursiven Aufruf, wie dies Sheraff tut. AesingleDigit(number, ++counter)
zfrisch
2
richtig @zfrisch das verstehe ich jetzt. Vielen Dank, dass Sie sich die Zeit genommen haben, es zu erklären
chs242
35
Bitte wechseln Sie ++counterzu counter+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.
BlueRaja - Danny Pflughoeft
37

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:

let counter = 0
function singleDigit(num) {
  counter++;
  // ..
}

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 singleDigitzweimal aufrufen (Javascript ist Single-Threaded, so dass es nicht zu schwierig ist), aber dies ist ein Fehler, der darauf wartet, wenn Sie singleDigitspäter aktualisieren , um asynchron zu sein, und es fühlt sich auch so an hässlich.

Die Lösung besteht darin, die counterVariable außerhalb, aber nicht global zu deklarieren . Dies ist möglich, weil Javascript Verschlüsse hat:

function singleDigit(num) {
  let counter = 0; // outside but in a closure

  // use an inner function as the real recursive function:
  function recursion (num) {
    counter ++
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
      return counter            // return final count (terminate)
    }else{
      return recursion(number)  // recurse!
    }
  }

  return recursion(num); // start recursion
}

Dies ähnelt der globalen Lösung, erstellt jedoch bei jedem Aufruf singleDigit(der jetzt keine rekursive Funktion ist) eine neue Instanz der counterVariablen.

Slebetman
quelle
1
Die Zählervariable ist nur innerhalb der singleDigitFunktion verfügbar und bietet eine alternative saubere Möglichkeit, dies zu tun, ohne ein Argument imo zu übergeben. +1
AndrewL64
1
Da recursiones 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 sie Function#bindin einer teilweise angewendeten Funktion.
Customcommander
@customcommander Ja, ich habe dies im ersten Teil meiner Antwort zusammengefasst erwähnt - 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, Sie
erstellen
counter--wäre die traditionelle Art, Ihre Behauptung zu lösen, dass "nicht zweimal richtig aufgerufen werden kann"
MonkeyZeus
1
@MonkeyZeus Welchen Unterschied macht das? Wie würden Sie auch wissen, mit welcher Nummer der Zähler initialisiert werden soll, um zu sehen, dass es sich um die Anzahl handelt, die wir finden möchten?
Slebetman
22

Ein anderer Ansatz, da Sie alle Zahlen erzeugen, ist die Verwendung eines Generators.

Das letzte Element ist Ihre nauf eine einstellige Zahl reduzierte Zahl. Um zu zählen, wie oft Sie iteriert haben, lesen Sie einfach die Länge des Arrays.

const digits = [...to_single_digit(39)];
console.log(digits);
//=> [27, 14, 4]
<script>
function* to_single_digit(n) {
  do {
    n = [...String(n)].reduce((x, y) => x * y);
    yield n;
  } while (n > 9);
}
</script>


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.

singleDigit(1024);       //=> 0
singleDigit(9876543210); //=> 0

// possible solution: String(n).includes('0')

Das Gleiche gilt für alle Zahlen, die 1nur aus bestehen.

singleDigit(11);    //=> 1
singleDigit(111);   //=> 1
singleDigit(11111); //=> 1

// possible solution: [...String(n)].every(n => n === '1')

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:

[...String(39)].reduce((x, y) => x * y)
//=> 27

[...String(-39)].reduce((x, y) => x * y)
//=> NaN

Mögliche Lösung:

const mult = n =>
  [...String(Math.abs(n))].reduce((x, y) => x * y, n < 0 ? -1 : 1)

mult(39)
//=> 27

mult(-39)
//=> -27
Customcommander
quelle
groß. @customcommander danke, dass du das sehr deutlich erklärt hast
chs242
6

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:

  {
    digit: 4,
    steps: [39, 27, 14, 4],
    calls: 3
  }

Sie können die Schritte dann bei Bedarf protokollieren oder zur weiteren Verarbeitung speichern.

Hier ist eine Version, die das macht:

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])

console .log (singleDigit (39))

Beachten Sie, dass wir das verfolgen, stepsaber das ableiten calls. Während wir die Anzahl der Anrufe mit einem zusätzlichen Parameter verfolgen konnten, scheint dies nichts zu gewinnen. Wir überspringen auch den map(Number)Schritt - diese werden in jedem Fall durch Multiplikation zu Zahlen gezwungen.

Wenn Sie Bedenken haben, dass dieser Standardparameter stepsals Teil Ihrer API verfügbar gemacht wird, können Sie ihn mithilfe einer internen Funktion wie der folgenden leicht ausblenden:

const singleDigit = (n) => {
  const recur = (n, steps) => 
    n <= 9
      ? {digit: n, steps: [... steps, n], calls: steps .length}
      : recur ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])
  return recur (n, [])
}

In beiden Fällen kann es etwas sauberer sein, die Ziffernmultiplikation in eine Hilfsfunktion zu extrahieren:

const digitProduct = (n) => [... (n + '')] .reduce ((a, b) => a * b)

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit (digitProduct(n), [... steps, n])
Scott Sauyet
quelle
2
Eine weitere gute Antwort;) Bitte beachten Sie, dass wenn n negativ ist, digitProductreturn NaN( -39 ~> ('-' * '3') * '9'). Daher möchten Sie möglicherweise einen absoluten Wert von n verwenden und -1oder 1als Anfangswert Ihrer Reduzierung verwenden.
Customcommander
@customcommander: tatsächlich wird es zurückkehren {"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.
Scott Sauyet
6

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 <= 9als reduktionsbedürftig gilt. Daher singleDigit(8)wird count = 0und singleDigit(39)wird count = 3, genau wie das OP und die akzeptierte Antwort zeigen:

const singleDigit = (num) => {
    let count = 0, ret, x;
    while (num > 9) {
        ret = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            ret *= x;
        }
        num *= ret;
        count++;
        console.log(num);
    }
    console.log("Answer = " + num + ", count = " + count);
    return num;
}

Es ist nicht erforderlich, die Nummern 9 oder weniger (dh num <= 9) zu verarbeiten. Leider wird der OP-Code verarbeitet, num <= 9obwohl 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, .reduceda 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)und console.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öschen console.logbeschleunigte 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 verwenden console.logund die Ergebnisse in ein Array zu verschieben und .lengthfor zu verwenden count. 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)und console.log).

Anstatt zu verwenden, habe .reduceich 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 ...

const singleDigit2 = (num) => {
    let red, x, arr = [];
    do {
        red = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            red *= x;
        }
        num *= red;
        arr.push(num);
    } while (num > 9);
    return arr;
}

let ans = singleDigit2(39);
console.log("singleDigit2(39) = [" + ans + "],  count = " + ans.length );
 // Output: singleDigit2(39) = [27,14,4],  count = 3

Die obige Funktion läuft extrem schnell. Es ist ungefähr 3,13x schneller als das OP (ohne .map(Number)und console.log) und ungefähr 8,4x schneller als die Antwort von customcommander . Beachten Sie, dass das Löschen console.logaus 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

Zuhälter Trizkit
quelle
1
Diese Antwort hat viel Bildungswert, also danke dafür. 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.
Customcommander
Danke, glaube ich. IMHO, das Lesen der tatsächlichen Mathematik, wie es geht, war für mich leichter zu verstehen .. als die [...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.
Pimp Trizkit
Hat JavaScript nicht eine Ganzzahldivision, sodass Sie das Äquivalent von C digit = num%10; num /= 10;ausführen können ? Wenn Sie num - xzuerst 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.
Peter Cordes
Ich glaube nicht. Das sind vars (JS hat keine ints). Daher n /= 10;wird nbei Bedarf in einen Float konvertiert . num = num/10 - x/10könnte es in einen Float umwandeln, was die lange Form der Gleichung ist. Daher muss ich die num = (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. Es digit = 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.
Pimp Trizkit
6

Warum rufen Sie nicht console.countin Ihrer Funktion an?

Bearbeiten: Snippet zum Ausprobieren in Ihrem Browser:

function singleDigit(num) {
    console.count("singleDigit");

    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)

Ich habe es in Chrome 79 und Firefox 72 funktionieren

Mistermatt
quelle
console.count würde nicht helfen, da der Zähler bei jedem Aufruf der Funktion zurückgesetzt wird (wie in den obigen Antworten erläutert)
chs242
2
Ich verstehe Ihr Problem nicht, da es in Chrome und Firefox funktioniert. Ich habe meiner Antwort einen Ausschnitt hinzugefügt
Mistermatt
6

Sie können hierfür den Verschluss verwenden.

Einfach counterin den Funktionsschluss speichern .

Hier ist ein Beispiel:

function singleDigitDecorator() {
	let counter = 0;

	return function singleDigitWork(num, isCalledRecursively) {

		// Reset if called with new params 
		if (!isCalledRecursively) {
			counter = 0;
		}

		counter++; // *

		console.log(`called ${counter} times`);

		let number = [...(num + "")].map(Number).reduce((x, y) => {
			return x * y;
		});

		if (number <= 9) {
			console.log(number);
		} else {
			console.log(number);

			return singleDigitWork(number, true);
		}
	};
}

const singleDigit = singleDigitDecorator();

singleDigit(39);

console.log('`===========`');

singleDigit(44);

Kholiavko
quelle
1
Auf diese Weise zählt der Zähler beim nächsten Anruf weiter und muss bei jedem ersten Anruf zurückgesetzt werden. Es führt zu einer schwierigen Frage: Wie kann man feststellen, wann eine rekursive Funktion aus einem anderen Kontext aufgerufen wird, in diesem Fall global vs function?
RobG
Dies ist nur ein Beispiel, um einen Gedanken zu entwickeln. Es kann geändert werden, indem der Benutzer nach seinen Anforderungen gefragt wird.
Kholiavko
@RobG Ich verstehe deine Frage nicht. Die rekursive Funktion kann nicht außerhalb des Verschlusses aufgerufen werden, da es sich um eine innere Funktion handelt. Es gibt also keine Möglichkeit oder Notwendigkeit, den Kontext zu unterscheiden, da es nur einen möglichen Kontext gibt
Slebetman
@slebetman Der Zähler wird niemals zurückgesetzt. Die von zurückgegebene Funktion singleDigitDecorator()erhöht bei jedem Aufruf denselben Zähler.
Customcommander
1
@ slebetman - Das Problem ist, dass die von singleDigitDecorator zurückgegebene Funktion ihren Zähler nicht zurücksetzt, wenn sie erneut aufgerufen wird. Dies ist die Funktion, die wissen muss, wann der Zähler zurückgesetzt werden muss. Andernfalls ist für jede Verwendung eine neue Instanz der Funktion erforderlich. Ein möglicher Anwendungsfall für Function.caller ? ;-)
RobG
1

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:

from functools import reduce

def single_digit(n: int) -> tuple:
    """Take an integer >= 0 and return a tuple of the single-digit product reduction
    and the number of reductions performed."""

    def _single_digit(n, i):
        if n <= 9:
            return n, i
        else:
            digits = (int(d) for d in str(n))
            product = reduce(lambda x, y: x * y, digits)
            return _single_digit(product, i + 1)

    return _single_digit(n, 0)

>>> single_digit(39)
(4, 3)
Luke Sawczak
quelle
1
In Python, würde ich so etwas wie lieber diese .
Phipsgabler