高橋  花子

高橋 花子

1638305040

JavaScriptでのRecursion (再帰)の理解

再帰は、威圧的に聞こえる可能性のあるプログラミングトピックの1つです。これは、プログラミングに不慣れな場合に特に当てはまります。このチュートリアルでは、それについて知る必要があるすべてを学びます。再帰とは何か、JavaScriptでの再帰のしくみ、そしてそれを実装する方法を学びます。
 

簡単な紹介

再帰とは何かを説明する最も簡単な方法は、それ自体を呼び出す関数であると言うことです。このタイプの関数は「再帰関数」と呼ばれます。JavaScriptまたは他の言語での再帰であるかどうかは関係ありません。主なアイデアは、関数があり、この関数が少なくとも1回はそれ自体を呼び出すということです。

// Simple recursive function
function recursiveFunction() {
  // Call the recursive function again
  recursiveFunction()
}

// Call the recursiveFunction()
recursiveFunction()

とはいえ、再帰関数は単なる関数ではありません。すべての再帰関数が満たさなければならないいくつかの条件があります。これは、その関数を再帰と呼ぶことができるようにするためだけに必要なわけではありません。また、その再帰を適切に機能させる必要があります。これが潜在的な問題です。

あなたが関数を持っているとしましょう。この関数はそれ自体を呼び出します。この関数を呼び出すとどうなりますか?まあ、それは自分自身を呼び出すでしょう。次は何が起こる?その関数がそれ自体を呼び出すとき、それは何度も何度もそれ自体を呼び出します。問題は、関数が終了するポイントがないことです。結果は無限ループです。

たとえば、上記の例の関数を実行しようとすると、これが発生します。その関数を実行すると、エラーが発生しますUncaught RangeError: Maximum call stack size exceeded。再帰関数にベースケースを追加することで、この問題を回避し、無限ループを作成できます。

規範事例

基本ケースは、特定の条件の空想的な名前です。「基本状態」とも呼ばれます。この条件により、関数は2つのことのいずれかを実行するように強制されます。条件がと評価されたfalse場合、再帰関数はそれ自体を再度呼び出します。条件がと評価されたtrue場合、再帰関数は値を返します。

この基本ケースを作成する最も簡単な方法は、単純なif…elseステートメントを使用することです。1つのブロック内で、ifまたはelse条件に応じて、何らかの値を返します。他のブロック内で、再帰関数を再度呼び出します。これにより、適切なタイミングで機能を終了できます。

// Simple recursive function
function recursiveFunction() {
  // Add base case
  if (/* condition */) {
    // Call the recursive function again
    recursiveFunction()
  } else {
    // Return something instead of calling
    // the recursive function again
  }
}

// Call the recursive function
recursiveFunction()

JavaScriptは、returnステートメントを検出すると関数の実行を終了します。これは、if...elseステートメントを実際に使用する必要がないことを意味します。必要なのはそのif部分だけです。何かがあれば、何かを返します。それ以外の場合は、JavaScriptにをスキップしif...elseて続行させることができます。

// Recursive function with shorter condition
function recursiveFunction() {
  // Add base case
  if (/* condition */) {
    // If condition evaluates to true
    // terminate this function call
    // by returning something
    return /* some value */
  }

  // Otherwise, call the recursive function again
  recursiveFunction()
}

// Call the recursive function
recursiveFunction()

これは実際には最短バージョンではありません。基本条件と機能全体をさらに短くすることができます。if...elseステートメントを三項演算子に置き換えることができます。このようにして、再帰関数全体をほぼ1つのライナーに減らすことができます。文字通りワンライナーよりも矢印関数を使用する場合。

// Recursive function with ternary operator
function recursiveFunction() {
  // Add base case
  return (/* condition */) ? /* some value */ : recursiveFunction()
}

// Call the recursive function
recursiveFunction()

最適なベースケースの選び方

ベースケースの最良の候補は何ですか?これは、再帰関数で何を達成したいかによって異なります。たとえば、再帰を使用して階乗を計算するとします。これは再帰の最も一般的な例です。階乗の場合は、使用できる最小の数を考えてください。

階乗の場合、最小数は1です。階乗1(1!)は、常に1になります。これにより、1は、到達できる最小の数またはレベルであるため、ベースケースの最適な候補になります。Xから0までの数を数えたい場合は、0が最小の数になります。また、ベースケースの最適な候補になります。

反対のことをして上向きに数えたい場合は、ベースが到達したい最大の数になります。別の例は、単純な文字列を逆にすることです。その場合、基本的なケースは、文字列の長さが0より大きくなければならないということです。空の文字列を逆にし続けることは意味がありません。

実際の仕組み:コールスタックの簡単な紹介

あなたは再帰が何であるか、そしてそれがどのように見えるかを知っているので、それを見たときにそれを認識することができます。また、ベースケースとは何かを知っています。それでは、実際にどのように機能するかを見てみましょう。特に、JavaScriptでどのように機能するか。これは、最もよく知っているプログラミング言語になるためです。

再帰がどのように機能するかを理解するには、コールスタックについて少なくとも少し知っておく必要があります。コールスタックは、JavaScriptに組み込まれているメカニズムです。JavaScriptはそれを使用して、すべての関数呼び出しを追跡します。関数を呼び出すとしましょう。これを行うと、JavaScriptはその関数を呼び出しスタックに追加します。

その関数呼び出しが終了すると、JavaScriptはその関数呼び出しを呼び出しスタックから自動的に削除し、存在する場合は下の別の関数呼び出しに移動します。ただし、呼び出した関数が別の関数を呼び出すと、別のことが起こります。その2番目の関数が呼び出されると、JavaScriptはそれを呼び出しスタックにも追加します。

その2番目の関数も関数を呼び出す場合、JavaScriptはそれを呼び出しスタックの一番上にも追加します。これは、現在の関数チェーンに関数呼び出しがある限り繰り返されます。知っておくべき重要なことが3つあります。まず、JavaScriptは2番目の呼び出しを最初の呼び出しの上に配置します。

JavaScriptは、その関数呼び出しをその上、呼び出しスタック全体の上に追加します。2つ目は、JavaScriptがコールスタック内の呼び出しを上から下に実行することです。これは、呼び出しスタックに追加された最初の関数呼び出しが最後に実行されることを意味します。

逆に、呼び出しスタックに追加された最後の関数呼び出しが最初に実行されます。これはLIFOの原則後入れ先出し)と呼ばれます。3つ目は、JavaScriptが関数呼び出しを検出すると、現在の呼び出しの実行を停止し、その新しい呼び出し、および新しく呼び出された関数内のすべてを実行することです。

その新しく呼び出された関数が実行された場合にのみ、JavaScriptは前の呼び出しに戻り、その実行を終了します。これは、呼び出しスタック内の関数ごとに繰り返されます。

function funcFour() {
  // some code to execute
}

function funcThree() {
  funcFour()
  // Execution of funcThree() is paused on the line above
  // until funcFour() is finished
}

function funcTwo() {
  funcThree()
  // Execution of funcTwo() is paused on the line above
  // until funcThree() is finished
}

function funcOne() {
  funcTwo()
  // Execution of funcOne() is paused on the line above
  // until funcTwo() is finished
}

// Call the funcOne()
funcOne()

// Call stack at this moment:
// funcFour() - executed as first (top of the stack)
// funcThree() - waiting for funcFour() to finish
// funcTwo() - waiting for funcThree() to finish
// funcOne() - waiting for funcTwo() to finish

// README:
// funcFour() is at the top of the stack
// and its function call will be finished as first
// after that execution will return to funcThree()
// when funcThree() is finished execution will return to funcTwo()
// when funcTwo() is finished execution will return to funcOne()
// when funcOne() is finished the call stack will be empty

再帰的階乗関数、呼び出しスタック、および分析

それでは、コールスタックに関するこの情報を使用して、JavaScriptの再帰がどのように機能するかを理解しましょう。これをよりよく説明するために、再帰関数を使用して階乗を計算してみましょう。この関数は、階乗を計算するための数値である単一のパラメーターを受け入れます。

この関数の基本的なケースは、引数として渡した数値が1に等しくなければならないということです。この状況が発生すると、関数はその数値を返します。1を返します。それ以外の場合は、引数として渡された数値を1減らして、自分自身を呼び出した結果を掛けた数値を返します。

// Recursive function to calculate factorial
function calculateFactorial(num) {
  // Base case
  if (num === 1) {
    // The value of "num" here will be 1
    return num
  }

  return num * calculateFactorial(num - 1)
}

// Shorter version with ternary operator
function calculateFactorial(num) {
  // Base case
  return (num === 1) ? num : num * calculateFactorial(num - 1)
}

// Test the calculateFactorial()
calculateFactorial(4)
// Output:
// 24

// Test the calculateFactorial() again
calculateFactorial(9)
// Output:
// 362880

// Test the calculateFactorial() one more time
calculateFactorial(1)
// Output:
// 1

calculateFactorial()関数の実行を分析してみましょう。これを短くするために、階乗を計算する数として4を使用しましょう。引数として番号4を使用して関数を呼び出すと、JavaScriptはそれを呼び出しスタックに追加します。4は1に等しくないためcalculateFactorial()、再度呼び出されます。

現時点でcalculateFactorial()は、は4番ではなく、3番が引数として渡されて呼び出されます。後続の呼び出しは常に番号が1減少します。JavaScriptはその2番目の呼び出しも呼び出しスタックに追加します。前回のcalculateFactorial()4番の通話の上に追加します。

数はまだ1に等しくありません。したがって、calculateFactorial()関数の別の呼び出しが実行されます。引数として渡される番号は2になります。JavaScriptはこの呼び出しを呼び出しスタックの一番上に追加し、calculateFactorial()関数を再度呼び出します。番号は1になります。

この番号は基本ケースに適合しているため、calculateFactorial()関数は番号を返し、それ自体を再度呼び出すことはありません。これで一連の呼び出しが終了し、呼び出しスタックの最上位になりました。

// Recursive function to calculate factorial
function calculateFactorial(num) {
  // Base case
  return (num === 1) ? return num : num * calculateFactorial(num - 1)
}

// Test the calculateFactorial()
calculateFactorial(4)

// Call stack after calling calculateFactorial(4):
// calculateFactorial(1) - top of the stack, first out
// calculateFactorial(2)
// calculateFactorial(3)
// calculateFactorial(4) - bottom of the stack, last out

次は何が起こる?スタックの一番上にいて、それ以上呼び出しがなくなると、JavaScriptはスタックの一番下に移動し始めます。この間、JavaScriptはスタック内のすべての関数呼び出しの値も返し始めます。戻り値ごとに、1つの関数呼び出しがスタックから削除されます。

最も興味深い部分は、これらすべての呼び出しから返される値です。関数のnum * calculateFactorial(num - 1)コードの行を覚えていcalculateFactorial()ますか?スタック内の呼び出しによって返されるこれらの値は、基本的にcalculateFactorial(num - 1)パーツを置き換えます。

線は次のようになりnum * "num" (returned by the previous call)ます。スタック内の呼び出しごとnumに、は前の呼び出しの結果で乗算されます。これcalculateFactorial(1)はスタックの最上位での最後の呼び出しであり、その戻り値が最初に返されます。

以前の呼び出しはなく、関数はこの番号を返す必要があることを示しています。これがその(num === 1) ? return num :一部です。したがって、最初の戻り値は1です。次の呼び出しは呼び出しスタックにありcalculateFactorial(2)ます。これが最後の呼び出しではないため、この(num === 1) ? return num :行はここでは適用されません。

代わりに、を適用する必要がありますnum * calculateFactorial(num - 1)。1つ目numは、現在の呼び出しにパラメーターとして渡された番号です。2。calculateFactorial(num - 1)は、最後の呼び出しによって返された番号です。1。したがって、num * calculateFactorial(num - 1)結果は2 * 1。になります。

コールスタックの次の呼び出しはcalculateFactorial(3)です。前の場合と同様に、を適用する必要がありますnum * calculateFactorial(num - 1)。1つ目numは、現在の呼び出しに渡された番号です:3。これcalculateFactorial(num - 1)は、最後の呼び出しによって返された番号です:2。

最後の呼び出しの結果はでした2 * 1。そのため、calculateFactorial(num - 1)現在は2に変換されます。したがって、num * calculateFactorial(num - 1)に変換され3 * 2ます。このcalculateFactorial(4)呼び出しは、スタックの一番下にある最後の呼び出しでした。num現在のコールに渡されたが4です。

結果は、calculateFactorial(num - 1)前の呼び出しによって返された、calculateFactorial(3)6(の結果でした3 * 2)。したがって、今、にnum * calculateFactorial(num - 1)変換され4 * 6ます。これにより、現在および最後の呼び出しによって返される値が24になります。これは、階乗計算の最終結果でもあります。

// Recursive function to calculate factorial
function calculateFactorial(num) {
  // Base case
  return (num === 1) ? return num : num * calculateFactorial(num - 1)
}

// Test the calculateFactorial()
calculateFactorial(4)

// Call stack after calling calculateFactorial(4):
// calculateFactorial(1)
//  - returns 1

// calculateFactorial(2)
// - returns 2 * 1 (1 is value returned from calculateFactorial(1))

// calculateFactorial(3)
//  - returns 3 * 2 (2 is value returned from calculateFactorial(2))

// calculateFactorial(4)
//  - returns 4 * 6 (6 is value returned from calculateFactorial(4))

JavaScriptでの再帰の別の2つの例

このチュートリアルを終了する前に、JavaScriptでの再帰の例をいくつか見てみましょう。再帰を使用して任意の数の階乗を計算する方法をすでに知っています。再帰関数の別の2つの例を簡単に見てみましょう。

カウントダウンの再帰関数

JavaScriptでの再帰の実装を示す良い例の1つは、0までカウントダウンし、再帰呼び出しごとに数値を出力する関数です。この再帰関数の基本ケースは、渡された数が1減少したときに、0より大きい場合です。

数値が0より大きい場合にのみ、関数が再度呼び出されます。それ以外の場合は、これ以上何もすることがないため、関数は自動的に終了します。

// Recursive function for countdown
function countdown(num) {
  // Print the number passed
  // to the current recursive call
  console.log(num)

  // Base case
  if (num - 1 > 0) {
    // If current number decreased by 1
    // is higher than 0 call countdown() again
    // with number decreased by 1
    return countdown(num - 1)
  }
}

// Call the countdown() function
countdown(11)
// Output:
// 11
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1

文字列を反転するための再帰関数

JavaScriptでの再帰の実装の2番目の例は、文字列を逆にする関数です。この関数は、パラメータとして文字列を受け入れます。基本的なケースは、文字列の長さが1より大きい場合です。この条件が真の場合、関数はそれ自体を呼び出します。

この後続の呼び出しの文字列は、最初の文字を含まない現在の呼び出しの文字列になります。さらに、この最初の文字は、次の呼び出しによって返される値の末尾に追加されます。

// Recursive function for reversing string
function reverseString(str) {
  // Base case
  if (str.length >= 1) {
    // If the length of the string is bigger than 1
    // call the reverseString() function again,
    // pass in pass in the string without the first character
    // and then add the character and the end
    return reverseString(str.substring(1)) + str.charAt(0)
  }

  // Otherwise, return the string
  return str
}

// Call the reverseString() function
reverseString('Hello')
// Output:
// 'olleH'

結論:JavaScriptでの再帰の概要

再帰は高度なトピックであり、完全に把握するのは非常に難しい場合があります。しかし、それについて学ぶことを学ぶのは時間の価値があります。再帰は、いくつかの問題をより良く、より速く解決するための非常に便利なツールです。このチュートリアルが、JavaScriptの動作の再帰とそれが一般的に何であるかを理解するのに役立つことを願っています。

この記事が気に入ったら、今後の投稿を見逃さないように購読してください。

リンク: https://blog.alexdevero.com/recursion-in-javascript/

#javascript 

What is GEEK

Buddha Community

JavaScriptでのRecursion (再帰)の理解
高橋  花子

高橋 花子

1638305040

JavaScriptでのRecursion (再帰)の理解

再帰は、威圧的に聞こえる可能性のあるプログラミングトピックの1つです。これは、プログラミングに不慣れな場合に特に当てはまります。このチュートリアルでは、それについて知る必要があるすべてを学びます。再帰とは何か、JavaScriptでの再帰のしくみ、そしてそれを実装する方法を学びます。
 

簡単な紹介

再帰とは何かを説明する最も簡単な方法は、それ自体を呼び出す関数であると言うことです。このタイプの関数は「再帰関数」と呼ばれます。JavaScriptまたは他の言語での再帰であるかどうかは関係ありません。主なアイデアは、関数があり、この関数が少なくとも1回はそれ自体を呼び出すということです。

// Simple recursive function
function recursiveFunction() {
  // Call the recursive function again
  recursiveFunction()
}

// Call the recursiveFunction()
recursiveFunction()

とはいえ、再帰関数は単なる関数ではありません。すべての再帰関数が満たさなければならないいくつかの条件があります。これは、その関数を再帰と呼ぶことができるようにするためだけに必要なわけではありません。また、その再帰を適切に機能させる必要があります。これが潜在的な問題です。

あなたが関数を持っているとしましょう。この関数はそれ自体を呼び出します。この関数を呼び出すとどうなりますか?まあ、それは自分自身を呼び出すでしょう。次は何が起こる?その関数がそれ自体を呼び出すとき、それは何度も何度もそれ自体を呼び出します。問題は、関数が終了するポイントがないことです。結果は無限ループです。

たとえば、上記の例の関数を実行しようとすると、これが発生します。その関数を実行すると、エラーが発生しますUncaught RangeError: Maximum call stack size exceeded。再帰関数にベースケースを追加することで、この問題を回避し、無限ループを作成できます。

規範事例

基本ケースは、特定の条件の空想的な名前です。「基本状態」とも呼ばれます。この条件により、関数は2つのことのいずれかを実行するように強制されます。条件がと評価されたfalse場合、再帰関数はそれ自体を再度呼び出します。条件がと評価されたtrue場合、再帰関数は値を返します。

この基本ケースを作成する最も簡単な方法は、単純なif…elseステートメントを使用することです。1つのブロック内で、ifまたはelse条件に応じて、何らかの値を返します。他のブロック内で、再帰関数を再度呼び出します。これにより、適切なタイミングで機能を終了できます。

// Simple recursive function
function recursiveFunction() {
  // Add base case
  if (/* condition */) {
    // Call the recursive function again
    recursiveFunction()
  } else {
    // Return something instead of calling
    // the recursive function again
  }
}

// Call the recursive function
recursiveFunction()

JavaScriptは、returnステートメントを検出すると関数の実行を終了します。これは、if...elseステートメントを実際に使用する必要がないことを意味します。必要なのはそのif部分だけです。何かがあれば、何かを返します。それ以外の場合は、JavaScriptにをスキップしif...elseて続行させることができます。

// Recursive function with shorter condition
function recursiveFunction() {
  // Add base case
  if (/* condition */) {
    // If condition evaluates to true
    // terminate this function call
    // by returning something
    return /* some value */
  }

  // Otherwise, call the recursive function again
  recursiveFunction()
}

// Call the recursive function
recursiveFunction()

これは実際には最短バージョンではありません。基本条件と機能全体をさらに短くすることができます。if...elseステートメントを三項演算子に置き換えることができます。このようにして、再帰関数全体をほぼ1つのライナーに減らすことができます。文字通りワンライナーよりも矢印関数を使用する場合。

// Recursive function with ternary operator
function recursiveFunction() {
  // Add base case
  return (/* condition */) ? /* some value */ : recursiveFunction()
}

// Call the recursive function
recursiveFunction()

最適なベースケースの選び方

ベースケースの最良の候補は何ですか?これは、再帰関数で何を達成したいかによって異なります。たとえば、再帰を使用して階乗を計算するとします。これは再帰の最も一般的な例です。階乗の場合は、使用できる最小の数を考えてください。

階乗の場合、最小数は1です。階乗1(1!)は、常に1になります。これにより、1は、到達できる最小の数またはレベルであるため、ベースケースの最適な候補になります。Xから0までの数を数えたい場合は、0が最小の数になります。また、ベースケースの最適な候補になります。

反対のことをして上向きに数えたい場合は、ベースが到達したい最大の数になります。別の例は、単純な文字列を逆にすることです。その場合、基本的なケースは、文字列の長さが0より大きくなければならないということです。空の文字列を逆にし続けることは意味がありません。

実際の仕組み:コールスタックの簡単な紹介

あなたは再帰が何であるか、そしてそれがどのように見えるかを知っているので、それを見たときにそれを認識することができます。また、ベースケースとは何かを知っています。それでは、実際にどのように機能するかを見てみましょう。特に、JavaScriptでどのように機能するか。これは、最もよく知っているプログラミング言語になるためです。

再帰がどのように機能するかを理解するには、コールスタックについて少なくとも少し知っておく必要があります。コールスタックは、JavaScriptに組み込まれているメカニズムです。JavaScriptはそれを使用して、すべての関数呼び出しを追跡します。関数を呼び出すとしましょう。これを行うと、JavaScriptはその関数を呼び出しスタックに追加します。

その関数呼び出しが終了すると、JavaScriptはその関数呼び出しを呼び出しスタックから自動的に削除し、存在する場合は下の別の関数呼び出しに移動します。ただし、呼び出した関数が別の関数を呼び出すと、別のことが起こります。その2番目の関数が呼び出されると、JavaScriptはそれを呼び出しスタックにも追加します。

その2番目の関数も関数を呼び出す場合、JavaScriptはそれを呼び出しスタックの一番上にも追加します。これは、現在の関数チェーンに関数呼び出しがある限り繰り返されます。知っておくべき重要なことが3つあります。まず、JavaScriptは2番目の呼び出しを最初の呼び出しの上に配置します。

JavaScriptは、その関数呼び出しをその上、呼び出しスタック全体の上に追加します。2つ目は、JavaScriptがコールスタック内の呼び出しを上から下に実行することです。これは、呼び出しスタックに追加された最初の関数呼び出しが最後に実行されることを意味します。

逆に、呼び出しスタックに追加された最後の関数呼び出しが最初に実行されます。これはLIFOの原則後入れ先出し)と呼ばれます。3つ目は、JavaScriptが関数呼び出しを検出すると、現在の呼び出しの実行を停止し、その新しい呼び出し、および新しく呼び出された関数内のすべてを実行することです。

その新しく呼び出された関数が実行された場合にのみ、JavaScriptは前の呼び出しに戻り、その実行を終了します。これは、呼び出しスタック内の関数ごとに繰り返されます。

function funcFour() {
  // some code to execute
}

function funcThree() {
  funcFour()
  // Execution of funcThree() is paused on the line above
  // until funcFour() is finished
}

function funcTwo() {
  funcThree()
  // Execution of funcTwo() is paused on the line above
  // until funcThree() is finished
}

function funcOne() {
  funcTwo()
  // Execution of funcOne() is paused on the line above
  // until funcTwo() is finished
}

// Call the funcOne()
funcOne()

// Call stack at this moment:
// funcFour() - executed as first (top of the stack)
// funcThree() - waiting for funcFour() to finish
// funcTwo() - waiting for funcThree() to finish
// funcOne() - waiting for funcTwo() to finish

// README:
// funcFour() is at the top of the stack
// and its function call will be finished as first
// after that execution will return to funcThree()
// when funcThree() is finished execution will return to funcTwo()
// when funcTwo() is finished execution will return to funcOne()
// when funcOne() is finished the call stack will be empty

再帰的階乗関数、呼び出しスタック、および分析

それでは、コールスタックに関するこの情報を使用して、JavaScriptの再帰がどのように機能するかを理解しましょう。これをよりよく説明するために、再帰関数を使用して階乗を計算してみましょう。この関数は、階乗を計算するための数値である単一のパラメーターを受け入れます。

この関数の基本的なケースは、引数として渡した数値が1に等しくなければならないということです。この状況が発生すると、関数はその数値を返します。1を返します。それ以外の場合は、引数として渡された数値を1減らして、自分自身を呼び出した結果を掛けた数値を返します。

// Recursive function to calculate factorial
function calculateFactorial(num) {
  // Base case
  if (num === 1) {
    // The value of "num" here will be 1
    return num
  }

  return num * calculateFactorial(num - 1)
}

// Shorter version with ternary operator
function calculateFactorial(num) {
  // Base case
  return (num === 1) ? num : num * calculateFactorial(num - 1)
}

// Test the calculateFactorial()
calculateFactorial(4)
// Output:
// 24

// Test the calculateFactorial() again
calculateFactorial(9)
// Output:
// 362880

// Test the calculateFactorial() one more time
calculateFactorial(1)
// Output:
// 1

calculateFactorial()関数の実行を分析してみましょう。これを短くするために、階乗を計算する数として4を使用しましょう。引数として番号4を使用して関数を呼び出すと、JavaScriptはそれを呼び出しスタックに追加します。4は1に等しくないためcalculateFactorial()、再度呼び出されます。

現時点でcalculateFactorial()は、は4番ではなく、3番が引数として渡されて呼び出されます。後続の呼び出しは常に番号が1減少します。JavaScriptはその2番目の呼び出しも呼び出しスタックに追加します。前回のcalculateFactorial()4番の通話の上に追加します。

数はまだ1に等しくありません。したがって、calculateFactorial()関数の別の呼び出しが実行されます。引数として渡される番号は2になります。JavaScriptはこの呼び出しを呼び出しスタックの一番上に追加し、calculateFactorial()関数を再度呼び出します。番号は1になります。

この番号は基本ケースに適合しているため、calculateFactorial()関数は番号を返し、それ自体を再度呼び出すことはありません。これで一連の呼び出しが終了し、呼び出しスタックの最上位になりました。

// Recursive function to calculate factorial
function calculateFactorial(num) {
  // Base case
  return (num === 1) ? return num : num * calculateFactorial(num - 1)
}

// Test the calculateFactorial()
calculateFactorial(4)

// Call stack after calling calculateFactorial(4):
// calculateFactorial(1) - top of the stack, first out
// calculateFactorial(2)
// calculateFactorial(3)
// calculateFactorial(4) - bottom of the stack, last out

次は何が起こる?スタックの一番上にいて、それ以上呼び出しがなくなると、JavaScriptはスタックの一番下に移動し始めます。この間、JavaScriptはスタック内のすべての関数呼び出しの値も返し始めます。戻り値ごとに、1つの関数呼び出しがスタックから削除されます。

最も興味深い部分は、これらすべての呼び出しから返される値です。関数のnum * calculateFactorial(num - 1)コードの行を覚えていcalculateFactorial()ますか?スタック内の呼び出しによって返されるこれらの値は、基本的にcalculateFactorial(num - 1)パーツを置き換えます。

線は次のようになりnum * "num" (returned by the previous call)ます。スタック内の呼び出しごとnumに、は前の呼び出しの結果で乗算されます。これcalculateFactorial(1)はスタックの最上位での最後の呼び出しであり、その戻り値が最初に返されます。

以前の呼び出しはなく、関数はこの番号を返す必要があることを示しています。これがその(num === 1) ? return num :一部です。したがって、最初の戻り値は1です。次の呼び出しは呼び出しスタックにありcalculateFactorial(2)ます。これが最後の呼び出しではないため、この(num === 1) ? return num :行はここでは適用されません。

代わりに、を適用する必要がありますnum * calculateFactorial(num - 1)。1つ目numは、現在の呼び出しにパラメーターとして渡された番号です。2。calculateFactorial(num - 1)は、最後の呼び出しによって返された番号です。1。したがって、num * calculateFactorial(num - 1)結果は2 * 1。になります。

コールスタックの次の呼び出しはcalculateFactorial(3)です。前の場合と同様に、を適用する必要がありますnum * calculateFactorial(num - 1)。1つ目numは、現在の呼び出しに渡された番号です:3。これcalculateFactorial(num - 1)は、最後の呼び出しによって返された番号です:2。

最後の呼び出しの結果はでした2 * 1。そのため、calculateFactorial(num - 1)現在は2に変換されます。したがって、num * calculateFactorial(num - 1)に変換され3 * 2ます。このcalculateFactorial(4)呼び出しは、スタックの一番下にある最後の呼び出しでした。num現在のコールに渡されたが4です。

結果は、calculateFactorial(num - 1)前の呼び出しによって返された、calculateFactorial(3)6(の結果でした3 * 2)。したがって、今、にnum * calculateFactorial(num - 1)変換され4 * 6ます。これにより、現在および最後の呼び出しによって返される値が24になります。これは、階乗計算の最終結果でもあります。

// Recursive function to calculate factorial
function calculateFactorial(num) {
  // Base case
  return (num === 1) ? return num : num * calculateFactorial(num - 1)
}

// Test the calculateFactorial()
calculateFactorial(4)

// Call stack after calling calculateFactorial(4):
// calculateFactorial(1)
//  - returns 1

// calculateFactorial(2)
// - returns 2 * 1 (1 is value returned from calculateFactorial(1))

// calculateFactorial(3)
//  - returns 3 * 2 (2 is value returned from calculateFactorial(2))

// calculateFactorial(4)
//  - returns 4 * 6 (6 is value returned from calculateFactorial(4))

JavaScriptでの再帰の別の2つの例

このチュートリアルを終了する前に、JavaScriptでの再帰の例をいくつか見てみましょう。再帰を使用して任意の数の階乗を計算する方法をすでに知っています。再帰関数の別の2つの例を簡単に見てみましょう。

カウントダウンの再帰関数

JavaScriptでの再帰の実装を示す良い例の1つは、0までカウントダウンし、再帰呼び出しごとに数値を出力する関数です。この再帰関数の基本ケースは、渡された数が1減少したときに、0より大きい場合です。

数値が0より大きい場合にのみ、関数が再度呼び出されます。それ以外の場合は、これ以上何もすることがないため、関数は自動的に終了します。

// Recursive function for countdown
function countdown(num) {
  // Print the number passed
  // to the current recursive call
  console.log(num)

  // Base case
  if (num - 1 > 0) {
    // If current number decreased by 1
    // is higher than 0 call countdown() again
    // with number decreased by 1
    return countdown(num - 1)
  }
}

// Call the countdown() function
countdown(11)
// Output:
// 11
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1

文字列を反転するための再帰関数

JavaScriptでの再帰の実装の2番目の例は、文字列を逆にする関数です。この関数は、パラメータとして文字列を受け入れます。基本的なケースは、文字列の長さが1より大きい場合です。この条件が真の場合、関数はそれ自体を呼び出します。

この後続の呼び出しの文字列は、最初の文字を含まない現在の呼び出しの文字列になります。さらに、この最初の文字は、次の呼び出しによって返される値の末尾に追加されます。

// Recursive function for reversing string
function reverseString(str) {
  // Base case
  if (str.length >= 1) {
    // If the length of the string is bigger than 1
    // call the reverseString() function again,
    // pass in pass in the string without the first character
    // and then add the character and the end
    return reverseString(str.substring(1)) + str.charAt(0)
  }

  // Otherwise, return the string
  return str
}

// Call the reverseString() function
reverseString('Hello')
// Output:
// 'olleH'

結論:JavaScriptでの再帰の概要

再帰は高度なトピックであり、完全に把握するのは非常に難しい場合があります。しかし、それについて学ぶことを学ぶのは時間の価値があります。再帰は、いくつかの問題をより良く、より速く解決するための非常に便利なツールです。このチュートリアルが、JavaScriptの動作の再帰とそれが一般的に何であるかを理解するのに役立つことを願っています。

この記事が気に入ったら、今後の投稿を見逃さないように購読してください。

リンク: https://blog.alexdevero.com/recursion-in-javascript/

#javascript 

野村  陽一

野村 陽一

1636165680

【Python】セット(集合体)の理解

Pythonのセットは、複数の要素を1つの要素に格納するために使用されます。セットは、順序付け も インデックス付けもされていないコレクションです。セットは中括弧で定義できます。

内包表記に関しては、リスト内包表記はPythonでリストを作成するために最もよく使用される手法です。また、あまり一般的ではない辞書内包表記もあります。それでも、この記事で見るように、設定された理解もあります。

Pythonセットの理解

Pythonのセット内包表記は、既存のセット、リスト、または辞書に基づいてセットを定義および作成するための洗練された方法です。異なるセット理解の重要な観点は、セットを返すことです。つまり、セット内のアイテムは順序付けられておらず、重複を含めることはできません。

構文

{expression(variable) for variable in input_set [predicate][, …]}

パラメーター

表現

これはオプションの引数であり、述語式の条件を満たす入力セットのメンバーから新しいセットのメンバーを作成する出力式です。

 

変数

これは、入力セットのメンバーを表す変数である必須パラメーターです。

input_set

これは、入力セットを表す必須の引数です。

 

述語

これは、入力セットのメンバーのフィルターとして機能するオプションのパラメーターです。

[、…]]

これは、別のネストされた内包表記であるオプションの引数です。

simpsons = "Homer Simpson is son of Abraham Simpson and Father of Bart Simpson"
chars = simpsons.split()
simpsons_set = {word for word in chars}
print(simpsons_set)

出力

{'son', 'Bart', 'is', 'Abraham', 'Father', 'Simpson', 'of', 'Homer', 'and'}

この例では、最初に文字列を定義してから、文字列をリストに分割 するstring split()関数を使用しました。次に、セット内包表記を使用して、リストアイテムからセットを作成しました。

気づいたら、文に現れた単語の最初の順序が結果に保持されていなかったことがわかります。このプログラムを何度も実行すると、毎回異なる出力が見つかります。

SetComprehension内に条件を追加する

セット内包表記を使用してセットを作成しながら、カスタム条件を追加できます。長さが4以上の単語のみが必要だとします。4未満の単語は除外されます。これを行うには、比較演算子を使用できます。

simpsons = "Homer Simpson is son of Abraham Simpson and Father of Bart Simpson"
chars = simpsons.split()
simpsons_set = {word for word in chars if len(word) >= 4}
print(simpsons_set)

出力

{'Bart', 'Simpson', 'Abraham', 'Father', 'Homer'}

文字列の長さが4以上の単語のみが出力に含まれていることがわかります。

Pythonでのネストされたセットの理解

Pythonでは、内部セットと呼ばれるセット内のセットをフリーズセットにする必要があります。そうしないと、エラーが発生します。

フリーズされたセットはセットと同じですが、セットは変更可能であり、フリーズされたセットは変更可能ではありません。 

つまり、セットは変更可能であるため、ハッシュ化できないため、凍結しない限り、より大きなセット内のアイテムとして存在することはできません。幸いなことに、frozenset()関数でセットを囲むことにより、セットをフリーズセットにすることができ ます。

simpsons = "Homer Simpson is son of Abraham Simpson and Father of Bart Simpson"
chars = simpsons.split()
vowels = ['a', 'e', 'i', 'o', 'u']
constants = {frozenset(
    {letter for letter in word if letter not in vowels}) for word in chars}
print(constants)

出力

{frozenset({'s'}), frozenset({'s', 'n'}), 
 frozenset({'H', 'm', 'r'}), 
 frozenset({'f'}), 
 frozenset({'d', 'n'}), 
 frozenset({'t', 'h', 'r', 'F'}), 
 frozenset({'S', 's', 'n', 'p', 'm'}), 
 frozenset({'r', 'h', 'b', 'm', 'A'}), 
 frozenset({'B', 't', 'r'})}

セット内のセットを処理する場合は、frozenset()が必要になることに注意してください。リストまたは辞書内のセットは、凍結されたセットを必要としません。

結論

セット内包表記は、Pythonでセットを作成するためのよりコンパクトで高速な方法です。コードがユーザーフレンドリーであることを保証するために、1行で非常に長いリスト内包表記を避ける必要があります。forループを使用して、設定された理解コードを毎回置き換えることはできますが、forループコードを毎回設定された理解コードに置き換えることはできません。

SetComprehensionの例は以上です。

リンク: https://appdividend.com/2020/12/03/python-set-comprehension/

#python 

宇野  和也

宇野 和也

1635150929

Promise.all と Async/Await の理解

データベースからすべてのユーザーを返し、完了するまでにある程度の時間がかかるAPI呼び出しがあるとします。

// First promise returns an array after a delay
const getUsers = () => {
  return new Promise((resolve, reject) => {
    return setTimeout(
      () => resolve([{ id: 'jon' }, { id: 'andrey' }, { id: 'tania' }]),
      600
    )
  })
}

ここで、ユーザーの配列全体に存在する情報に依存する別の呼び出しがあります。

// Second promise relies on the result of first promise
const getIdFromUser = (user) => {
  return new Promise((resolve, reject) => {
    return setTimeout(() => resolve(user.id), 500)
  })
}

そして、2番目の呼び出しを変更する3番目の呼び出し。

// Third promise relies on the result of the second promise
const capitalizeIds = (id) => {
  return new Promise((resolve, reject) => {
    return setTimeout(() => resolve(id.toUpperCase()), 200)
  })
}

最初の呼び出しfor...ofを実行してから、ループを使用して、それに依存する後続の呼び出しを実行しようとする場合があります。

const runAsyncFunctions = async () => {
  const users = await getUsers()

  for (let user of users) {
    const userId = await getIdFromUser(user)
    console.log(userId)

    const capitalizedId = await capitalizeIds(userId)
    console.log(capitalizedId)
  }

  console.log(users)
}

runAsyncFunctions()

ただし、これは私の出力になります。

jon JON andrey ANDREY tania TANIA (3) [{…}, {…}, {…}]

Promise.all()代わりに、最初のすべての関数、次にすべての2番目の関数、次にすべての3番目の関数を実行するために使用できます。

const runAsyncFunctions = async () => {
  const users = await getUsers()

  Promise.all(
    users.map(async (user) => {
      const userId = await getIdFromUser(user)
      console.log(userId)

      const capitalizedId = await capitalizeIds(userId)
      console.log(capitalizedId)
    })
  )

  console.log(users)
}
(3) [{…}, {…}, {…}] jon andrey tania JON ANDREY TANIA

それが誰かを助けることを願っています。

これが、コンソールで実行できるスニペット全体です。

// First promise returns an array
const getUsers = () => {
  return new Promise((resolve, reject) => {
    return setTimeout(
      () => resolve([{ id: 'jon' }, { id: 'andrey' }, { id: 'tania' }]),
      600
    )
  })
}

// Second promise relies on the resulting array of first promise
const getIdFromUser = (user) => {
  return new Promise((resolve, reject) => {
    return setTimeout(() => resolve(user.id), 500)
  })
}

// Third promise relies on the result of the second promise
const capitalizeIds = (id) => {
  return new Promise((resolve, reject) => {
    return setTimeout(() => resolve(id.toUpperCase()), 200)
  })
}

const runAsyncFunctions = async () => {
  const users = await getUsers()

  Promise.all(
    users.map(async (user) => {
      const userId = await getIdFromUser(user)
      console.log(userId)

      const capitalizedId = await capitalizeIds(userId)
      console.log(capitalizedId)
    })
  )

  console.log(users)
}

runAsyncFunctions()

リンク: https://www.taniarascia.com/promise-all-with-async-await/

#async