Выразительный JavaScript (Хавербеке) - страница 28

Вот вам загадка: можно получить бесконечное количество чисел, начиная с числа 1, и потом либо добавляя 5, либо умножая на 3. Как нам написать функцию, которая, получив число, пытается найти последовательность таких сложений и умножений, которые приводят к заданному числу? К примеру, число 13 можно получить, сначала умножив 1 на 3, а затем добавив 5 два раза. А число 15 вообще нельзя так получить.

Рекурсивное решение:

>function findSolution(target) {

>  function find(start, history) {

>    if (start == target)

>      return history;

>    else if (start > target)

>      return null;

>    else

>      return find(start + 5, "(" + history + " + 5)") ||

>             find(start * 3, "(" + history + " * 3)");

>  }

>  return find(1, "1");

>}


>console.log(findSolution(24));

>// → (((1 * 3) + 5) * 3)

Этот пример не обязательно находит самое короткое решение – он удовлетворяется любым. Не ожидаю, что вы сразу поймёте, как программа работает. Но давайте разбираться в этом отличном упражнении на рекурсивное мышление.

Внутренняя функция >find занимается рекурсией. Она принимает два аргумента – текущее число и строку, которая содержит запись того, как мы пришли к этому номеру. И возвращает либо строчку, показывающую нашу последовательность шагов, либо >null.

Для этого функция выполняет одно из трёх действий. Если заданное число равно цели, то текущая история как раз и является способом её достижения, поэтому она и возвращается. Если заданное число больше цели, продолжать умножения и сложения смысла нет, потому что так оно будет только увеличиваться. А если мы ещё не достигли цели, функция пробует оба возможных пути, начинающихся с заданного числа. Она дважды вызывает себя, один раз с каждым из способов. Если первый вызов возвращает не >null, он возвращается. В другом случае возвращается второй.

Чтобы лучше понять, как функция достигает нужного эффекта, давайте просмотрим её вызовы, которые происходят в поисках решения для числа 13.

>find(1, "1")

>  find(6, "(1 + 5)")

>    find(11, "((1 + 5) + 5)")

>      find(16, "(((1 + 5) + 5) + 5)")

>        too big

>      find(33, "(((1 + 5) + 5) * 3)")

>        too big

>    find(18, "((1 + 5) * 3)")

>      too big

>  find(3, "(1 * 3)")

>    find(8, "((1 * 3) + 5)")

>      find(13, "(((1 * 3) + 5) + 5)")

>        found!

Отступ показывает глубину стека вызовов. В первый раз функция find вызывает сама себя дважды, чтобы проверить решения, начинающиеся с >(1 + 5) и >(1 * 3). Первый вызов ищет решение, начинающееся с >(1 + 5), и при помощи рекурсии проверяет все решения, выдающие число, меньшее или равное требуемому. Не находит, и возвращает