0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>В видео есть неточность: в формуле вычисления факториала и в коде, который на ней основан, не учитывается, что для 0 (нуля) тоже можно вычислить факториал - он равен 1 (единице). Исправленный код есть ниже в конспекте.</p>
1
<p>В видео есть неточность: в формуле вычисления факториала и в коде, который на ней основан, не учитывается, что для 0 (нуля) тоже можно вычислить факториал - он равен 1 (единице). Исправленный код есть ниже в конспекте.</p>
2
<h2>Транскрипт урока</h2>
2
<h2>Транскрипт урока</h2>
3
<p>Рекурсию можно использовать разными способами: тот, который мы рассматривали в предыдущем уроке, называется<strong>рекурсивным процессом</strong>. Сегодняшний урок будет более понятен, если вы уже разобрались с предыдущей темой и выполнили упражнение.</p>
3
<p>Рекурсию можно использовать разными способами: тот, который мы рассматривали в предыдущем уроке, называется<strong>рекурсивным процессом</strong>. Сегодняшний урок будет более понятен, если вы уже разобрались с предыдущей темой и выполнили упражнение.</p>
4
<p>Другой способ использования рекурсии в коде называется<strong>итеративным процессом</strong>. Название запутывает: и рекурсивный и итеративный процесс - оба описывают рекурсию.</p>
4
<p>Другой способ использования рекурсии в коде называется<strong>итеративным процессом</strong>. Название запутывает: и рекурсивный и итеративный процесс - оба описывают рекурсию.</p>
5
<p>Помните наборы вызовов из предыдущего урока. Каждый новый созданный экземпляр, или ящик функции factorial ожидает от следующего экземпляра, что тот сделает возврат какого-нибудь значения. Никакого вычисления не производится, пока мы не спустимся до конца, к базовому случаю. Только тогда мы получим 1 и начнем выполнять умножения в обратном порядке: 1 умноженная на 2 - это 2, затем 3 умножается на два, получается 6.</p>
5
<p>Помните наборы вызовов из предыдущего урока. Каждый новый созданный экземпляр, или ящик функции factorial ожидает от следующего экземпляра, что тот сделает возврат какого-нибудь значения. Никакого вычисления не производится, пока мы не спустимся до конца, к базовому случаю. Только тогда мы получим 1 и начнем выполнять умножения в обратном порядке: 1 умноженная на 2 - это 2, затем 3 умножается на два, получается 6.</p>
6
<p>С факториалом 3 никаких проблем, но представьте, что нужен факториал 100. Программе потребуется хранить в памяти множество чисел из-за откладывания всех операций умножения.<strong>Откладывание</strong>здесь - ключевое слово: суть<strong>рекурсивного процесса</strong>в откладывании вычислений до самого конца. Ничего не будет умножаться, пока процесс не спустится к базовому случаю, а если его остановить, программа ничего не будет знать и вы не получите никакой полезной информации, так как не дадите ей полностью закончить задачу. Рекурсивный процесс похож на человека, который выполняет работу за всю неделю в пятницу после обеда.</p>
6
<p>С факториалом 3 никаких проблем, но представьте, что нужен факториал 100. Программе потребуется хранить в памяти множество чисел из-за откладывания всех операций умножения.<strong>Откладывание</strong>здесь - ключевое слово: суть<strong>рекурсивного процесса</strong>в откладывании вычислений до самого конца. Ничего не будет умножаться, пока процесс не спустится к базовому случаю, а если его остановить, программа ничего не будет знать и вы не получите никакой полезной информации, так как не дадите ей полностью закончить задачу. Рекурсивный процесс похож на человека, который выполняет работу за всю неделю в пятницу после обеда.</p>
7
<p>Вся эта информация о вычислениях называется<strong>состоянием</strong>. Все, что ваша программа помнит в конкретный момент времени - это состояние: вычисления, константы, функции. И очень часто состояние - это причина самых разных проблем в программировании.</p>
7
<p>Вся эта информация о вычислениях называется<strong>состоянием</strong>. Все, что ваша программа помнит в конкретный момент времени - это состояние: вычисления, константы, функции. И очень часто состояние - это причина самых разных проблем в программировании.</p>
8
<p>Оставлять все дела на пятничный полдень - не лучший способ работать. Способ получше - делать понемногу в понедельник, еще немного во вторник и дальше в том же духе. Это<strong>итеративный процесс</strong>: когда работа распределяется равномерно на всю неделю.</p>
8
<p>Оставлять все дела на пятничный полдень - не лучший способ работать. Способ получше - делать понемногу в понедельник, еще немного во вторник и дальше в том же духе. Это<strong>итеративный процесс</strong>: когда работа распределяется равномерно на всю неделю.</p>
9
<p>Давайте запишем ту же функцию факториала используя итеративный процесс. Идея в том, чтобы не откладывать умножения, а умножить два числа сразу и передать результат в следующий шаг.</p>
9
<p>Давайте запишем ту же функцию факториала используя итеративный процесс. Идея в том, чтобы не откладывать умножения, а умножить два числа сразу и передать результат в следующий шаг.</p>
10
<p>Вот код.</p>
10
<p>Вот код.</p>
11
<p>Как вы видите, все выглядит уже не как математическая формула факториала. И это не похоже на функцию рекурсивного процесса из прошлого урока. Так обычно и бывает: код для рекурсивного процесса легче читать и понимать, поскольку он более близок к<strong>идее</strong>. Но он недостаточно эффективный. Итеративный процесс намного эффективнее, но он более усложненный.</p>
11
<p>Как вы видите, все выглядит уже не как математическая формула факториала. И это не похоже на функцию рекурсивного процесса из прошлого урока. Так обычно и бывает: код для рекурсивного процесса легче читать и понимать, поскольку он более близок к<strong>идее</strong>. Но он недостаточно эффективный. Итеративный процесс намного эффективнее, но он более усложненный.</p>
12
<p>Функция факториала содержит в себе другую функцию. Помните, определения функций - это не сами ящики, а всего лишь их описания. Внутренняя функция iter принимает два аргумента: counter и accumulator. Counter отслеживает движение от n до 1. А accumulator - текущий результат умножения чисел от n до 1. Если counter достигает 1, accumulator возвращается - в этот момент он будет равен конечному ответу.</p>
12
<p>Функция факториала содержит в себе другую функцию. Помните, определения функций - это не сами ящики, а всего лишь их описания. Внутренняя функция iter принимает два аргумента: counter и accumulator. Counter отслеживает движение от n до 1. А accumulator - текущий результат умножения чисел от n до 1. Если counter достигает 1, accumulator возвращается - в этот момент он будет равен конечному ответу.</p>
13
<p>После того, как функция определена, у нас остается единственная строка в функции факториала: вернуть результат вызова функции iter с n и 1 в качестве аргументов.</p>
13
<p>После того, как функция определена, у нас остается единственная строка в функции факториала: вернуть результат вызова функции iter с n и 1 в качестве аргументов.</p>
14
<p>Давайте запустим factorial(3). Единственная строка, которая на самом деле запускается в функции factorial это return iter(n, 1). Она хочет вернуть и завершиться, но ей нужно получить значение от функции iter.</p>
14
<p>Давайте запустим factorial(3). Единственная строка, которая на самом деле запускается в функции factorial это return iter(n, 1). Она хочет вернуть и завершиться, но ей нужно получить значение от функции iter.</p>
15
<p>Создается ящик iter. Он принимает 3 и 1. Внутри ящика iter 3 известно как counter, а 1 как acc. Значение counter не равно 1, поэтому первое условие не выполняется. Затем он хочет сделать возврат, но ему необходимо получить значение от нового экземпляра iter.</p>
15
<p>Создается ящик iter. Он принимает 3 и 1. Внутри ящика iter 3 известно как counter, а 1 как acc. Значение counter не равно 1, поэтому первое условие не выполняется. Затем он хочет сделать возврат, но ему необходимо получить значение от нового экземпляра iter.</p>
16
<p>Это самая важная часть: новый ящик вызывается с counter уменьшенным на 1, потому что мы прошли один шаг, а accumulator был умножен на counter.</p>
16
<p>Это самая важная часть: новый ящик вызывается с counter уменьшенным на 1, потому что мы прошли один шаг, а accumulator был умножен на counter.</p>
17
<p>Создается новый ящик iter. Он принимает 2 и 3 - counter и accumulator. Counter не равен 1, поэтому он пытается вернуть значение, но не может - ему нужно получить значение от нового экземпляра iter , вызванного с 2 - 1 в качестве первого аргумента, и 2 * 3 в качестве второго. Мы еще не закончили, но выполнили полезное умножение - 2 * 3 это одно из умножений, необходимых для нахождения 3!</p>
17
<p>Создается новый ящик iter. Он принимает 2 и 3 - counter и accumulator. Counter не равен 1, поэтому он пытается вернуть значение, но не может - ему нужно получить значение от нового экземпляра iter , вызванного с 2 - 1 в качестве первого аргумента, и 2 * 3 в качестве второго. Мы еще не закончили, но выполнили полезное умножение - 2 * 3 это одно из умножений, необходимых для нахождения 3!</p>
18
<p>Создается новый iter ящик. Он принимает 1 и 6. Теперь первое условие удовлетворено, поэтому этот ящик просто возвращает accumulator, число 6.</p>
18
<p>Создается новый iter ящик. Он принимает 1 и 6. Теперь первое условие удовлетворено, поэтому этот ящик просто возвращает accumulator, число 6.</p>
19
<p>Затем значение 6 просачивается в первый iter ящик, затем в ящик факториал, а затем возвращается в виде ответа.</p>
19
<p>Затем значение 6 просачивается в первый iter ящик, затем в ящик факториал, а затем возвращается в виде ответа.</p>
20
<p>Так выглядят вычисления шаг за шагом:</p>
20
<p>Так выглядят вычисления шаг за шагом:</p>
21
<p>iter(3, 1); // iter(3 - 1, 3 * 1); iter(2, 3); // iter(2 - 1, 2 * 3); iter(1, 6); // counter === 1, return 6 6;</p>
21
<p>iter(3, 1); // iter(3 - 1, 3 * 1); iter(2, 3); // iter(2 - 1, 2 * 3); iter(1, 6); // counter === 1, return 6 6;</p>
22
<p>В любой момент программе необходимо помнить состояние, но его размер всегда неизменный - всего два числа.</p>
22
<p>В любой момент программе необходимо помнить состояние, но его размер всегда неизменный - всего два числа.</p>
23
<p>Подобный итеративный процесс в целом может быть описан так:</p>
23
<p>Подобный итеративный процесс в целом может быть описан так:</p>
24
<ol><li>Определить начальное состояние. В нашем случае мы делаем первый вызов iter с n и 1. Это начальное состояние.</li>
24
<ol><li>Определить начальное состояние. В нашем случае мы делаем первый вызов iter с n и 1. Это начальное состояние.</li>
25
<li>Проверить терминальный сценарий. Мы проверяем, не равен ли counter числу 1 и останавливаем рекурсию, когда он принимает значение 1.</li>
25
<li>Проверить терминальный сценарий. Мы проверяем, не равен ли counter числу 1 и останавливаем рекурсию, когда он принимает значение 1.</li>
26
<li>Определить новое состояние. Это то, как процесс двигается вперед. В нашем случае мы делаем еще один вызов iter с уменьшенным counter и умноженным accumulator. Два этих новых числа определяют новое состояние.</li>
26
<li>Определить новое состояние. Это то, как процесс двигается вперед. В нашем случае мы делаем еще один вызов iter с уменьшенным counter и умноженным accumulator. Два этих новых числа определяют новое состояние.</li>
27
<li>Повторить шаг 2.</li>
27
<li>Повторить шаг 2.</li>
28
</ol><p>И эта штука повторяется, пока не доберется до терминального сценария.</p>
28
</ol><p>И эта штука повторяется, пока не доберется до терминального сценария.</p>
29
<p>Давайте повторим вкратце.</p>
29
<p>Давайте повторим вкратце.</p>
30
<ol><li><strong>Рекурсия</strong>- это когда что-то содержит себя в своем описании.</li>
30
<ol><li><strong>Рекурсия</strong>- это когда что-то содержит себя в своем описании.</li>
31
<li><strong>Рекурсивный процесс</strong>- это процесс вычисления с отложенными вычислениями.</li>
31
<li><strong>Рекурсивный процесс</strong>- это процесс вычисления с отложенными вычислениями.</li>
32
<li><strong>Итеративный процесс</strong>- это процесс вычисления, когда состояние может быть описано фиксированным количеством значений.</li>
32
<li><strong>Итеративный процесс</strong>- это процесс вычисления, когда состояние может быть описано фиксированным количеством значений.</li>
33
</ol><p>Теперь, после короткого тестового задания будет вероятно самое сложное упражнение этого курса. Но я уверен, что вы его раскусите. А когда вы это сделаете, вы почувствуете себя немного героем, как это было со мной.</p>
33
</ol><p>Теперь, после короткого тестового задания будет вероятно самое сложное упражнение этого курса. Но я уверен, что вы его раскусите. А когда вы это сделаете, вы почувствуете себя немного героем, как это было со мной.</p>
34
<h2>Примечание</h2>
34
<h2>Примечание</h2>
35
<p><strong>Очень важно понять отличия между рекурсией, рекурсивным процессом и итеративным процессом.<a>Вот подробное объяснение в блоге Хекслета</a>.</strong></p>
35
<p><strong>Очень важно понять отличия между рекурсией, рекурсивным процессом и итеративным процессом.<a>Вот подробное объяснение в блоге Хекслета</a>.</strong></p>
36
<h2>Дополнение к уроку</h2>
36
<h2>Дополнение к уроку</h2>
37
<p>Обратите внимание, что условие в iter функции не включает ветвь else. Поэтому, если условие (counter === 1) не удовлетворяется, происходит переход к следующей строке и выполняется return iter(counter - 1, counter * acc).</p>
37
<p>Обратите внимание, что условие в iter функции не включает ветвь else. Поэтому, если условие (counter === 1) не удовлетворяется, происходит переход к следующей строке и выполняется return iter(counter - 1, counter * acc).</p>
38
<p>Это работает, потому что когда инструкция return исполнена, экземпляр функции выплевывает значение и исчезает. Больше ничего не будет происходить после того, как выполнится return.</p>
38
<p>Это работает, потому что когда инструкция return исполнена, экземпляр функции выплевывает значение и исчезает. Больше ничего не будет происходить после того, как выполнится return.</p>
39
<p>Поэтому, когда условие удовлетворяется, происходит выполнение return acc, а второй возврат уже не происходит.</p>
39
<p>Поэтому, когда условие удовлетворяется, происходит выполнение return acc, а второй возврат уже не происходит.</p>
40
<p>Вот еще пример:</p>
40
<p>Вот еще пример:</p>
41
<p>Эта функция будет всегда возвращать 100. Две других возвращаемых инструкции никогда не выполнятся, потому что экземпляр функции уничтожается после исполнения первого возврата. Только один возврат может производиться в любом конкретном экземпляре функции.</p>
41
<p>Эта функция будет всегда возвращать 100. Две других возвращаемых инструкции никогда не выполнятся, потому что экземпляр функции уничтожается после исполнения первого возврата. Только один возврат может производиться в любом конкретном экземпляре функции.</p>
42
<h2>Выводы</h2>
42
<h2>Выводы</h2>
43
<p>Вызовем функцию из предыдущего урока:</p>
43
<p>Вызовем функцию из предыдущего урока:</p>
44
<p>Процесс вычисления, который создает эта функция, называется<strong>рекурсивным процессом</strong>. Основная его идея -<strong>откладывание</strong>вычисления до самого конца.</p>
44
<p>Процесс вычисления, который создает эта функция, называется<strong>рекурсивным процессом</strong>. Основная его идея -<strong>откладывание</strong>вычисления до самого конца.</p>
45
<p>Вся информация о вычислениях, обо всем, что запоминает программа в любой конкретный момент (вычислениях, константах, функциях), называется<strong>состоянием</strong>. Множество проблем в программировании рождается из необходимости справиться с состоянием.</p>
45
<p>Вся информация о вычислениях, обо всем, что запоминает программа в любой конкретный момент (вычислениях, константах, функциях), называется<strong>состоянием</strong>. Множество проблем в программировании рождается из необходимости справиться с состоянием.</p>
46
<p>Суть<strong>итеративного процесса</strong>- вычисление с фиксированным количеством состояний.</p>
46
<p>Суть<strong>итеративного процесса</strong>- вычисление с фиксированным количеством состояний.</p>
47
<p>Идея:</p>
47
<p>Идея:</p>
48
<ol><li>Считаем от n до 1</li>
48
<ol><li>Считаем от n до 1</li>
49
<li>Берем число из предыдущего шага</li>
49
<li>Берем число из предыдущего шага</li>
50
<li>Умножаем это число на текущее число</li>
50
<li>Умножаем это число на текущее число</li>
51
<li>Передаем его в новый шаг</li>
51
<li>Передаем его в новый шаг</li>
52
<li>Когда counter достигает 1, число передается из предыдущего шага в ответ</li>
52
<li>Когда counter достигает 1, число передается из предыдущего шага в ответ</li>
53
</ol><p>Нам нужно с чего-то начать, потому что шаг 2 требует число из предыдущего шага, и мы начинаем с 1, потому что тогда n * 1 будет просто n:</p>
53
</ol><p>Нам нужно с чего-то начать, потому что шаг 2 требует число из предыдущего шага, и мы начинаем с 1, потому что тогда n * 1 будет просто n:</p>
54
<p>Вот так выглядят вызовы iter, когда происходит вычисление 3!:</p>
54
<p>Вот так выглядят вызовы iter, когда происходит вычисление 3!:</p>
55
<h3><strong>Итеративный процесс</strong>в целом:</h3>
55
<h3><strong>Итеративный процесс</strong>в целом:</h3>
56
<ol><li>Определить начальное состояние. В нашем случае мы делаем первый вызов iter с n и 1. Это начальное состояние.</li>
56
<ol><li>Определить начальное состояние. В нашем случае мы делаем первый вызов iter с n и 1. Это начальное состояние.</li>
57
<li> Проверить терминальный сценарий. Мы убеждаемся, что counter это 1 и останавливаем рекурсию, когда он достигает значения 1.</li>
57
<li> Проверить терминальный сценарий. Мы убеждаемся, что counter это 1 и останавливаем рекурсию, когда он достигает значения 1.</li>
58
<li> Определить новое состояние. Это то, как продвигается процесс. В нашем случае мы делаем еще один вызов iter с уменьшенным counter и умноженным accumulator. Два этих новых числа определяют новое состояние.</li>
58
<li> Определить новое состояние. Это то, как продвигается процесс. В нашем случае мы делаем еще один вызов iter с уменьшенным counter и умноженным accumulator. Два этих новых числа определяют новое состояние.</li>
59
<li>Повторить шаг 2. </li>
59
<li>Повторить шаг 2. </li>
60
</ol><h3>Резюме</h3>
60
</ol><h3>Резюме</h3>
61
<ol><li><strong>Рекурсия</strong>- это когда что-то содержит себя в своем описании.</li>
61
<ol><li><strong>Рекурсия</strong>- это когда что-то содержит себя в своем описании.</li>
62
<li><strong>Рекурсивный процесс</strong>- это процесс обработки данных с отложенными вычислениями.</li>
62
<li><strong>Рекурсивный процесс</strong>- это процесс обработки данных с отложенными вычислениями.</li>
63
<li><strong>Итеративный процесс</strong>- это процесс вычисления, когда состояние может быть описано фиксированным количеством значений.</li>
63
<li><strong>Итеративный процесс</strong>- это процесс вычисления, когда состояние может быть описано фиксированным количеством значений.</li>
64
</ol>
64
</ol>