Skip to content

Chapter 6.1 translation #15

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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!
附注:使用公式的解法显然是最快的,针对任何数字,它都只需要三个运算;关键时刻还是数学有用啊!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

量词修改:三个运算改成三次运算或三次操作?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

『三次操作』就可以吧。


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. The standard describes a "tail call" optimization: if the 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 and we don't need to remember its execution context. In that case `sumTo(100000)` is countable. But if your JavaScript engine does not support it, there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size.
又注:标准中关于“尾部调用”优化的描述是如果递归调用发生在函数的最后(如同上面的`sumTo`),那么外部调用函数就不用记忆执行的上下文背景,因为在调用返回后没有需要继续执行的代码。在这种情况下,即便是`sumTo(100000)`也不是什么问题。但如果你的JavaScript引擎不支持这样的优化,那么因为通常的引擎对堆栈的总数有限制,你会遇到“超过最大堆栈尺寸”的错误。
20 changes: 10 additions & 10 deletions 1-js/06-advanced-functions/01-recursion/01-sum-to/task.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
importance: 5
重要度: 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:
使用三种不同的解法:

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. 使用循环.
2. 使用递归,在`'n > 1`时, `sumTo(n) = n + sumTo(n-1)` .
3. 使用数学公式 [arithmetic progression](https://en.wikipedia.org/wiki/Arithmetic_progression) formula.

An example of the result:
结果示例:

```js
function sumTo(n) { /*... your code ... */ }

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 is `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` 作为基本值, 除了多算一步之外没有差别( `0` 的阶乘结果是 `1`:

```js run
function factorial(n) {
Expand Down
14 changes: 7 additions & 7 deletions 1-js/06-advanced-functions/01-recursion/02-factorial/task.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
importance: 4
重要度: 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!`
自然数的阶乘[factorial](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 @@ -12,11 +12,11 @@ alert( fib(7) ); // 13
// fib(77); // will be extremely slow!
```

...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)` 会将引擎挂起一段时间并消耗所有的 CPU 资源。

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,23 +25,23 @@ 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(5)` `fib(4)` 时,都需要 `fib(3)` 的值。所有 `fib(3)` 需要至少两次完全独立的调用与计算。

Here's the full recursion tree:
完整的递归调用可以从这里的树状图看出:

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

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
Expand All @@ -56,9 +56,9 @@ a b c
*/
```

Now we want to get `fib(4) = fib(2) + fib(3)`.
现在我们想通过 `fib(4) = fib(2) + fib(3)` 得到 `4` 的计算结果。

Let's shift the variables: `a,b` will get `fib(2),fib(3)`, and `c` will get their sum:
我们可以将 `fib(2),fib(3)` 在前一步计算出来的结果放入 `a,b` , 那么 `c` 所包含的就是他们的和:

```js no-beautify
a = b; // now a = fib(2)
Expand All @@ -71,7 +71,7 @@ c = a + b; // c = fib(4)
*/
```

The next step gives another sequence number:
再继续一轮,就可以得到下一个数列的值:

```js no-beautify
a = b; // now a = fib(3)
Expand All @@ -84,9 +84,9 @@ c = a + b; // c = fib(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`.
因为数列中的第一个、第二个值已经通过变量赋值 `a=1`, `b=1` 进行了硬编码,因此循环可以从 `i=3` 开始。

The approach is called [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming).
这种方法又称之为:自底向上的动态编程 [dynamic programming bottom-up](https://en.wikipedia.org/wiki/Dynamic_programming)
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
importance: 5
重要度: 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.
满足公式 <code>F<sub>n</sub> = F<sub>n-1</sub> + F<sub>n-2</sub></code> 的数列被称为: [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) , 也即:下一个数是前两个数的和。

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.
斐波拉契数列与黄金分割 [Golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) 以及很多身边的自然现象有关。

Write a function `fib(n)` that returns the `n-th` Fibonacci number.
请编写一个返回第n `n-th` 个斐波拉契数字的函数 `fib(n)`

An example of work:
示例:

```js
function fib(n) { /* your code */ }
Expand All @@ -22,4 +22,4 @@ 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 loose 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` 的角色就是专门用于列表的遍历变量,如同`for` 循环中的 `i`一样。

# 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.
事情的另外一面是:递归解法代码更短并且很多时候这也意味着更容易理解。
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
importance: 5
重要度: 5

---

# Output a single-linked list
# 输出单项链接的列表

Let's say we have a single-linked list (as described in the chapter <info:recursion>):
假设我们有一个单向链接列表,如同<info:recursion>章节中所描述的:

```js
let list = {
Expand All @@ -22,8 +22,8 @@ let list = {
};
```

Write a function `printList(list)` that outputs list items one-by-one.
编写一个函数 `printList(list)` ,它能一个一个地输出每个列表中的元素。

Make two variants of the solution: using a loop and using recursion.
请分别使用循环与递归实现。

What's better: with recursion or without it?
使用递归与不使用递归,哪个更好?
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Using a recursion
# 使用递归

The recursive logic is a little bit tricky here.
递归逻辑在这里有点绕。

We need to first output the rest of the list and *then* output the current one:
我们必须先输出列表的剩余部分,*然后* 再输出当前的这个元素:

```js run
let list = {
Expand Down Expand Up @@ -31,13 +31,13 @@ function printReverseList(list) {
printReverseList(list);
```

# Using a loop
# 使用循环

The loop variant is also a little bit more complicated then the direct output.
使用循环的解法与上个章节相比也会复杂些。

There is no way to get the last value in our `list`. We also can't "go back".
我们既不能直接获取列表`list`中的最后一个值,也不能“走回头路”。

So what we can do is to first go through the items in the direct order and rememeber them in an array, and then output what we remembered in the reverse order:
所以,我们要做的首先是按照正常次序遍历整个列表,将每个元素记忆在一个数组中,然后再将数组中的元素逆序输出:

```js run
let list = {
Expand Down Expand Up @@ -71,4 +71,4 @@ function printReverseList(list) {
printReverseList(list);
```

Please note that the recursive solution actually does exactly the same: it follows the list, remembers the items in the chain of nested calls (in the execution context stack), and then outputs them.
请注意,递归解法其实做法完全一样,它先沿着列表遍历,将列表中的每个元素记忆在嵌套调用中(即:执行上下文堆栈中),然后再输出他们。
Loading