Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursion and stack #159

Merged
merged 24 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
04b8cb2
Translate a part of article
mahdiHash Sep 28, 2021
ed3051b
Translate a part of article
mahdiHash Sep 29, 2021
515ad84
Merge branch 'javascript-tutorial:master' into master
mahdiHash Sep 30, 2021
5fcdd34
Translate a part of article
mahdiHash Sep 30, 2021
17cc7e1
Translate a part of article
mahdiHash Oct 1, 2021
923fc54
Translate a part of article
mahdiHash Oct 1, 2021
5dfd251
Translate a part of article
mahdiHash Oct 2, 2021
b04627a
Translate a part of article
mahdiHash Oct 3, 2021
dccf17d
Translate article
mahdiHash Oct 3, 2021
a0a8673
Translate task of "sum-to"
mahdiHash Oct 4, 2021
43f6242
Translate solution of "sum-to"
mahdiHash Oct 4, 2021
e4568a1
Translate task of "factorial"
mahdiHash Oct 4, 2021
d62d0dc
Translate solution of "factorial"
mahdiHash Oct 4, 2021
a58435c
Translate task of "fibonacci-numbers"
mahdiHash Oct 4, 2021
a453039
Translate solution of "fibonacci-numbers"
mahdiHash Oct 4, 2021
7e5b852
Translate task of "output-single-linked-list"
mahdiHash Oct 4, 2021
a67bd44
Translate solution of "output-single-linked-list"
mahdiHash Oct 4, 2021
aa26bd7
Translate task of "output-single-linked-list-reverse"
mahdiHash Oct 4, 2021
4978f4f
Translate solution of "output-single-linked-list-reverse"
mahdiHash Oct 4, 2021
c4fc445
Apply suggestions from code review
mahdiHash Oct 4, 2021
2ffc9af
Update 1-js/06-advanced-functions/01-recursion/article.md
mahdiHash Oct 4, 2021
c084a25
Change "stack" to "پشته"
mahdiHash Oct 4, 2021
0fe37e7
Change "stack" to "پشته"
mahdiHash Oct 4, 2021
fafd50f
Change "stack" to "پشته"
mahdiHash Oct 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions 1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The solution using a loop:
راه‌حل با استفاده از حلقه:

```js run
function sumTo(n) {
Expand All @@ -12,7 +12,7 @@ function sumTo(n) {
alert( sumTo(100) );
```

The solution using recursion:
راه‌حل با استفاده از بازگشت:

```js run
function sumTo(n) {
Expand All @@ -23,7 +23,7 @@ function sumTo(n) {
alert( sumTo(100) );
```

The solution using the formula: `sumTo(n) = n*(n+1)/2`:
راه‌حل با استفاده از فرمول `sumTo(n) = n*(n+1)/2`:

```js run
function sumTo(n) {
Expand All @@ -33,8 +33,8 @@ function sumTo(n) {
alert( sumTo(100) );
```

P.S. Naturally, the formula is the fastest solution. It uses only 3 operations for any number `n`. The math helps!
پی‌نوشت: به طور طبیعی، فرمول سریع‌ترین راه‌حل است. این فرمول فقط از 3 عمل برای هر عدد `n` استفاده می‌کند. ریاضی کمک می‌کند!

The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower.
راه‌حل حلقه از نظر سرعت دوم است. در هر دو نوع بازگشتی و حلقه ما اعداد یکسانی را جمع می‌زنیم. اما بازگشت، فراخوانی‌های تودرتو و مدیریت پشته اجرا را دخیل می‌کند. همچنین منابع بیشتری مصرف می‌کند پس کندتر است.

P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size.
پی‌نوشت دوم: بعضی از موتورها از بهینه‌سازی «فراخوانی دنباله‌دار» پشتیبانی می‌کنند: اگر یک فراخوانی بازگشتی دقیقا آخرین فراخوانی در تابع باشد (مانند `sumTo(100000)` بالا)، سپس تابع بیرونی نیازی نخواهد داشت که اجرا شدن را ادامه دهد پس موتور نیازی ندارد که زمینه‌اجرای آن را به یاد بسپارد. این موضوع بار را از دوش حافظه برمی‌دارد پس محاسبه `sumTo(100000)` ممکن می‌شود. اما اگر موتور جاوااسکریپت از بهینه‌سازی فراخوانی دنباله‌دار پشتیبانی نکند (اکثر آنها پشتیبانی نمی‌کنند)، یک ارور ایجاد می‌شود: از حداکثر اندازه پشته گذشتیم، چون معمولا یک محدودیت برای کل اندازه پشته وجود دارد.
22 changes: 11 additions & 11 deletions 1-js/06-advanced-functions/01-recursion/01-sum-to/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ importance: 5

---

# Sum all numbers till the given one
# مجموع را تا عدد داده شده پیدا کنید

Write a function `sumTo(n)` that calculates the sum of numbers `1 + 2 + ... + n`.
یک تابع `sumTo(n)` بنویسید که جمع اعداد `1 + 2 + ... + n` را حساب می‌کند.

For instance:
برای مثال:

```js no-beautify
sumTo(1) = 1
Expand All @@ -17,20 +17,20 @@ sumTo(4) = 4 + 3 + 2 + 1 = 10
sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050
```

Make 3 solution variants:
3 نوع راه‌حل بنویسید:

1. Using a for loop.
2. Using a recursion, cause `sumTo(n) = n + sumTo(n-1)` for `n > 1`.
3. Using the [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression) formula.
1. با استفاده از یک حلقه for.
2. با استفاده از بازگشت، چون به ازای `n > 1` داریم `sumTo(n) = n + sumTo(n-1)`.
3. با استفاده از فرمول [تصاعد حسابی](https://fa.wikipedia.org/wiki/تصاعد_حسابی).

An example of the result:
یک مثال از نتیجه:

```js
function sumTo(n) { /*... your code ... */ }
function sumTo(n) { /*... کد شما ... */ }

alert( sumTo(100) ); // 5050
```

P.S. Which solution variant is the fastest? The slowest? Why?
پی‌نوشت: کدام راه‌حل سریع‌ترین است؟ کدام کندترین؟ چرا؟

P.P.S. Can we use recursion to count `sumTo(100000)`?
پی‌نوشت دوم: آیا می‌توانیم از بازگشت برای محاسبه `sumTo(100000)` استفاده کنیم؟
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
By definition, a factorial `n!` can be written as `n * (n-1)!`.
با توجه به تعریف، فاکتوریل `n!` می‌تواند به عنوان `n * (n-1)!` نوشته شود.

In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`.
به عبارتی دیگر، نتیجه `factorial(n)` می‌تواند به صورت ضرب `n` در نتیجه `factorial(n-1)` محاسبه شود. و فراخوانی برای `n-1` می‌تواند به صورت بازگشتی کمتر و کمتر شود تا به `1` برسد.

```js run
function factorial(n) {
Expand All @@ -10,7 +10,7 @@ function factorial(n) {
alert( factorial(5) ); // 120
```

The basis of recursion is the value `1`. We can also make `0` the basis here, doesn't matter much, but gives one more recursive step:
اساس بازگشت مقدار `1` است. همچنین اینجا می‌توانیم `0` را اساس و پایه قرار دهیم، اهمیتی ندارد اما یک مرحله بازگشت بیشتری ایجاد می‌کند:

```js run
function factorial(n) {
Expand Down
12 changes: 6 additions & 6 deletions 1-js/06-advanced-functions/01-recursion/02-factorial/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ importance: 4

---

# Calculate factorial
# فاکتوریل را حساب کنید

The [factorial](https://en.wikipedia.org/wiki/Factorial) of a natural number is a number multiplied by `"number minus one"`, then by `"number minus two"`, and so on till `1`. The factorial of `n` is denoted as `n!`
[فاکتوریل](https://en.wikipedia.org/wiki/Factorial) یک عدد طبیعی، عددی است که در `"عدد منهای یک"` ضرب می‌شود سپس در `"عدد منهای دو"` و همینطور تا `1` ادامه می‌یابد. فاکتوریل `n` به `n!` نشان داده می‌شود.

We can write a definition of factorial like this:
می‌توانیم یک تعریف مانند این برای فاکتوریل بنویسیم:

```js
n! = n * (n - 1) * (n - 2) * ...*1
```

Values of factorials for different `n`:
مقدارهای فاکتوریل‌ها برای `n`های متفاوت:

```js
1! = 1
Expand All @@ -22,10 +22,10 @@ Values of factorials for different `n`:
5! = 5 * 4 * 3 * 2 * 1 = 120
```

The task is to write a function `factorial(n)` that calculates `n!` using recursive calls.
تکلیف این است که یک تابع `factorial(n)` بنویسیم که `n!` را با استفاده از فراخوانی‌های بازگشتی محاسبه می‌کند.

```js
alert( factorial(5) ); // 120
```

P.S. Hint: `n!` can be written as `n * (n-1)!` For instance: `3! = 3*2! = 3*2*1! = 6`
پی‌نوشت: راهنمایی: `n!` می‌تواند به صورت `n * (n-1)!` نوشته شود، برای مثال: `3! = 3*2! = 3*2*1! = 6`.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The first solution we could try here is the recursive one.
اولین راه‌حلی که می‌توانیم اینجا امتحان کنیم راه‌حل بازگشتی است.

Fibonacci numbers are recursive by definition:
اعداد فیبوناچی طبق تعریف بازگشتی هستند:

```js run
function fib(n) {
Expand All @@ -9,14 +9,14 @@ function fib(n) {

alert( fib(3) ); // 2
alert( fib(7) ); // 13
// fib(77); // will be extremely slow!
// fib(77); // !خیلی کند خواهد بود
```

...But for big values of `n` it's very slow. For instance, `fib(77)` may hang up the engine for some time eating all CPU resources.
...اما برای مقدارهای بزرگ `n` بسیار کند است. برای مثال، `fib(77)` ممکن است موتور را به دلیل مصرف تمام منابع پردازنده برای مدتی از کار بیاندازد.

That's because the function makes too many subcalls. The same values are re-evaluated again and again.
به دلیل اینکه تابع تعداد زیادی زیرفراخوانی ایجاد می‌کند. مقدارهای یکسان دوباره و دوباره ارزیابی می‌شوند.

For instance, let's see a piece of calculations for `fib(5)`:
برای مثال، بیایید یک قسمت از محاسبات را برای `fib(5)` ببینیم:

```js no-beautify
...
Expand All @@ -25,68 +25,68 @@ fib(4) = fib(3) + fib(2)
...
```

Here we can see that the value of `fib(3)` is needed for both `fib(5)` and `fib(4)`. So `fib(3)` will be called and evaluated two times completely independently.
اینجا می‌توانیم ببینیم که مقدار `fib(3)` هم برای `fib(5)` نیاز است و هم برای `fib(4)`. پس `fib(3)` دو بار به صورت کاملا مستقل فراخوانی خواهد شد.

Here's the full recursion tree:
اینجا درخت بازگشت کامل را داریم:

![fibonacci recursion tree](fibonacci-recursion-tree.svg)

We can clearly notice that `fib(3)` is evaluated two times and `fib(2)` is evaluated three times. The total amount of computations grows much faster than `n`, making it enormous even for `n=77`.
می‌توانیم به وضوح ببینیم که `fib(3)` دو بار و `fib(2)` سه بار ارزیابی می‌شود. کل تعداد محاسبات نسبت به `n` خیلی سریع‌تر رشد می‌کند و آن را برای `n=77` بسیار عظیم می‌کند.

We can optimize that by remembering already-evaluated values: if a value of say `fib(3)` is calculated once, then we can just reuse it in future computations.
ما می‌توانیم با به خاطر سپردن مقدارهایی که از قبل ارزیابی شده‌اند آن را بهینه کنیم: اگر یک مقدار برای مثال `fib(3)` یک بار حساب شد، سپس ما می‌توانیم آن را در محاسبات بعدی دوباره استفاده کنیم.

Another variant would be to give up recursion and use a totally different loop-based algorithm.
یک نوع دیگر می‌تواند این باشد که بازگشت را ول کنیم و از یک الگوریتم متفاوت بر پایه حلقه استفاده کنیم.

Instead of going from `n` down to lower values, we can make a loop that starts from `1` and `2`, then gets `fib(3)` as their sum, then `fib(4)` as the sum of two previous values, then `fib(5)` and goes up and up, till it gets to the needed value. On each step we only need to remember two previous values.
به جای اینکه از `n` به مقدارهای کمتر برویم، می‌توانیم کاری کنیم که حلقه از `1` و `2` شروع کند سپس `fib(3)` را به عنوان مجموع آنها دریافت کند، سپس `fib(4)` به عنوان مجموع دو مقدار قبلی، سپس `fib(5)` و همینطور بالا می‌رود تا به مقدار مورد نیاز برسد. در هر مرحله ما فقط نیاز است که دو مقدار قبلی را به حافظه بسپاریم.

Here are the steps of the new algorithm in details.
اینجا مراحل الگوریتم جدید را با جزئیات داریم.

The start:
شروع:

```js
// a = fib(1), b = fib(2), these values are by definition 1
// a = fib(1) ،b = fib(2) ،این مقدارها طبق تعریف 1 هستند
let a = 1, b = 1;

// get c = fib(3) as their sum
// را به عنوان مجموع آنها دریافت کن c = fib(3)
let c = a + b;

/* we now have fib(1), fib(2), fib(3)
/* fib(1) ،fib(2) ،fib(3) حالا اینها را داریم
a b c
1, 1, 2
*/
```

Now we want to get `fib(4) = fib(2) + fib(3)`.
حالا می‌خواهیم `fib(4) = fib(2) + fib(3)` را دریافت کنیم.

Let's shift the variables: `a,b` will get `fib(2),fib(3)`, and `c` will get their sum:
بیایید متغیرها را تغییر دهیم: `a,b` مقدارهای `fib(2),fib(3)` را دریافت می‌کنند و `c` مجموع آنها را:

```js no-beautify
a = b; // now a = fib(2)
b = c; // now b = fib(3)
c = a + b; // c = fib(4)

/* now we have the sequence:
/* :حالا این دنباله را داریم
a b c
1, 1, 2, 3
*/
```

The next step gives another sequence number:
مرحله بعد یک دنباله عددی دیگر را می‌دهد:

```js no-beautify
a = b; // now a = fib(3)
b = c; // now b = fib(4)
c = a + b; // c = fib(5)

/* now the sequence is (one more number):
/* :حالا دنباله اینگونه است (یک عدد بیشتر)
a b c
1, 1, 2, 3, 5
*/
```

...And so on until we get the needed value. That's much faster than recursion and involves no duplicate computations.
...و همینطور تا زمانی که ما مقدار مورد نیاز را دریافت کنیم ادامه می‌یابد. این حلقه از بازگشتی سریع‌تر است و هیچ محاسبات تکراری را شامل نمی‌شود.

The full code:
کد کامل:

```js run
function fib(n) {
Expand All @@ -105,6 +105,6 @@ alert( fib(7) ); // 13
alert( fib(77) ); // 5527939700884757
```

The loop starts with `i=3`, because the first and the second sequence values are hard-coded into variables `a=1`, `b=1`.
حلقه با `i=3` شروع می‌شود چون اولین و دومین مقدار دنباله در متغیرهای `a=1`، `b=1` از قبل ذخیره شده‌اند.

The approach is called [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming).
این روش [برنامه نویسی پویا](https://fa.wikipedia.org/wiki/برنامه%E2%80%8Cنویسی_پویا) نامیده می‌شود.
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ importance: 5

---

# Fibonacci numbers
# اعداد فیبوناچی

The sequence of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) has the formula <code>F<sub>n</sub> = F<sub>n-1</sub> + F<sub>n-2</sub></code>. In other words, the next number is a sum of the two preceding ones.
دنباله [اعداد فیبوناجی](https://fa.wikipedia.org/wiki/اعداد_فیبوناچی) فرمول <code>F<sub>n</sub> = F<sub>n-1</sub> + F<sub>n-2</sub></code>. را دارد. به عبارتی دیگر، عدد بعدی حاصل جمع دو عدد قبل است.

First two numbers are `1`, then `2(1+1)`, then `3(1+2)`, `5(2+3)` and so on: `1, 1, 2, 3, 5, 8, 13, 21...`.
دو عدد اول `1` هستند، سپس `2(1+1)`، سپس `3(1+2)`، `5(2+3)` و غیره: `1, 1, 2, 3, 5, 8, 13, 21...`.

Fibonacci numbers are related to the [Golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) and many natural phenomena around us.
اعداد فیبوناچی به [نسبت طلایی](https://fa.wikipedia.org/wiki/نسبت_طلایی) و بسیاری از پدیده‌های دور و بر ما مربوط می‌شوند.

Write a function `fib(n)` that returns the `n-th` Fibonacci number.
تابع `fib(n)` را بنویسید که عدد `nاُم` را برگرداند.

An example of work:
یک مثال از کار:

```js
function fib(n) { /* your code */ }
function fib(n) { /* کد شما */ }

alert(fib(3)); // 2
alert(fib(7)); // 13
alert(fib(77)); // 5527939700884757
```

P.S. The function should be fast. The call to `fib(77)` should take no more than a fraction of a second.
پی‌نوشت: تابع باید سریع باشد. فراخوانی `fib(77)` باید بیشتر از کسری از ثانیه زمان نگیرد.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Loop-based solution
# راه‌حل بر اساس حلقه

The loop-based variant of the solution:
نوع حلقه‌ای راه‌حل:

```js run
let list = {
Expand Down Expand Up @@ -30,7 +30,7 @@ function printList(list) {
printList(list);
```

Please note that we use a temporary variable `tmp` to walk over the list. Technically, we could use a function parameter `list` instead:
لطفا در نظر داشته باشید که ما از متغیر موقتی `tmp` برای پیمایش در لیست استفاده می‌کنیم. از لحاظ فنی، ما می‌توانستیم از پارامتر `list` به جای آن استفاده کنیم:

```js
function printList(list) {
Expand All @@ -43,15 +43,15 @@ function printList(list) {
}
```

...But that would be unwise. In the future we may need to extend a function, do something else with the list. If we change `list`, then we lose such ability.
...اما این کار عاقلانه نیست. در آینده ممکن است تابعی ایجاد کنیم که کار دیگری با لیست انجام می‌دهد. اگر ما `list` را تغییر دهیم، سپس چنین امکانی را از دست می‌دهیم.

Talking about good variable names, `list` here is the list itself. The first element of it. And it should remain like that. That's clear and reliable.
صحبت دربارهٔ اسامی خوب برای متغیرها شد، `list` اینجا خودش یک لیست است. اولین المان آن. و بهتر است همان‌طور بماند. این‌طوری واضح و اتکاپذیر است.

From the other side, the role of `tmp` is exclusively a list traversal, like `i` in the `for` loop.
از سویی دیگر، نقش `tmp` فقط یک پیمایش لیست است مانند `i` در حلقه `for`.

# Recursive solution
# راه‌حل بازگشتی

The recursive variant of `printList(list)` follows a simple logic: to output a list we should output the current element `list`, then do the same for `list.next`:
نوع بازگشتی `printList(list)` از منطقی ساده پیروی می‌کند: برای نمایش یک لیست ما باید المان کنونی `list` را خروجی بگیریم سپس همین کار را برای `list.next` انجام دهیم:

```js run
let list = {
Expand All @@ -70,19 +70,19 @@ let list = {

function printList(list) {

alert(list.value); // output the current item
alert(list.value); // نمایش المان کنونی

if (list.next) {
printList(list.next); // do the same for the rest of the list
printList(list.next); // انجام کار یکسان برای بقیه‌ی لیست
}

}

printList(list);
```

Now what's better?
حالا کدام بهتر است؟

Technically, the loop is more effective. These two variants do the same, but the loop does not spend resources for nested function calls.
از لحاظ فنی، حلقه مفیدتر است. این دو نوع یک کار را انجام می‌دهند اما حلقه منابع را برای فراخوانی‌های تودرتوی تابع مصرف نمی‌کند.

From the other side, the recursive variant is shorter and sometimes easier to understand.
از سوی دیگر، نوع بازگشتی کوتاه‌تر است و بعضی اوقات برای فهمیدن راحت‌تر است.
Loading