HTML Diff
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>