Введение
Так как версия 5.5 на PHP такая замечательная вещь, как generators. Я не буду повторять официальную страницу руководства, но они отлично подходят для краткого определения итераторов. Наиболее известный пример:
function xrange($from, $till, $step)
{
if ($from>$till || $step<=0)
{
throw new InvalidArgumentException('Invalid range initializers');
}
for ($i = $from; $i < $till; $i += $step)
{
yield $i;
}
}
//...
foreach (xrange(2, 13, 3) as $i)
{
echo($i.PHP_EOL); // 2,5,8,11
}
и генератор фактически не является функцией, а экземпляром конкретного класса:
get_class(xrange(1, 10, 1)); // Generator
Проблема
Сделано с материалом RTM, теперь перейдем к моему вопросу. Представьте, что мы хотим создать генератор числа Фибоначчи. Обычно, чтобы получить их, мы можем использовать простую функцию:
function fibonacci($n)
{
if(!is_int($n) || $n<0)
{
throw new InvalidArgumentException('Invalid sequence limit');
}
return $n < 2 ? $n : fibonacci($n-1) + fibonacci($n-2);
}
var_dump(fibonacci(6)); // 8
Позвольте преобразовать это во что-то, что содержит последовательность, а не только последний член:
function fibonacci($n)
{
if (!is_int($n) || $n<0)
{
throw new InvalidArgumentException('Invalid sequence limit');
}
if ($n<2)
{
return range(0, $n);
}
$n1 = fibonacci($n-1);
$n2 = fibonacci($n-2);
return array_merge($n1, [array_pop($n1)+array_pop($n2)]);
}
//...
foreach (fibonacci(6) as $i)
{
echo($i.PHP_EOL); // 0,1,1,2,3,5,8
}
Теперь у нас есть функция, которая возвращает массив с полной последовательностью
Вопрос
Наконец, вопрос: как я могу преобразовать свою последнюю функцию fibonacci
, чтобы она давала мои значения, а не удерживала их в массиве? Мой $n
может быть большим, поэтому я хочу использовать преимущества генераторов, например, в xrange
sample. Псевдокод будет:
function fibonacci($n)
{
if (!is_int($n) || $n<0)
{
throw new InvalidArgumentException('Invalid sequence limit');
}
if ($n<2)
{
yield $n;
}
yield fibonacci($n-2) + fibonacci($n-1);
}
Но это, очевидно, дерьмо, потому что мы не можем справиться с ним так, потому что рекурсия вызовет объект класса Generator
, а не int
значение.
Бонус: получение последовательности фибоначчи - всего лишь образец для более общего вопроса: как использовать генераторы с рекурсией в общем случае? Конечно, я могу использовать стандартный Iterator для этого или переписать мою функцию, чтобы избежать рекурсии. Но я хочу добиться этого с помощью генераторов. Это возможно? Стоит ли это использовать таким образом?