В этом посте будут использоваться числа Фибоначчи в качестве инструмента, объясняющего полезность генераторов Python .
В этом посте будет представлен как код C ++, так и код Python.
Числа Фибоначчи определяются как последовательность: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Или вообще:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Это можно очень легко передать в функцию C ++:
size_t Fib(size_t n)
{
//Fib(0) = 0
if(n == 0)
return 0;
//Fib(1) = 1
if(n == 1)
return 1;
//Fib(N) = Fib(N-2) + Fib(N-1)
return Fib(n-2) + Fib(n-1);
}
Но если вы хотите напечатать первые шесть чисел Фибоначчи, вы будете пересчитывать множество значений с помощью вышеуказанной функции.
Например: Fib(3) = Fib(2) + Fib(1)
, но Fib(2)
также пересчитывает Fib(1)
. Чем выше значение, которое вы хотите вычислить, тем хуже для вас будет.
Так что можно поддаться искушению переписать вышесказанное, отслеживая состояние в main
.
// Not supported for the first two elements of Fib
size_t GetNextFib(size_t &pp, size_t &p)
{
int result = pp + p;
pp = p;
p = result;
return result;
}
int main(int argc, char *argv[])
{
size_t pp = 0;
size_t p = 1;
std::cout << "0 " << "1 ";
for(size_t i = 0; i <= 4; ++i)
{
size_t fibI = GetNextFib(pp, p);
std::cout << fibI << " ";
}
return 0;
}
Но это очень уродливо и усложняет нашу логику в main
. Было бы лучше не беспокоиться о состоянии в нашей функции main
.
Мы могли бы вернуть vector
значений и использовать iterator
для итерации по этому набору значений, но это требует много памяти сразу для большого количества возвращаемых значений.
Итак, вернемся к нашему старому подходу, что произойдет, если мы захотим сделать что-то еще, кроме печати чисел? Мы должны были бы скопировать и вставить весь блок кода в main
и изменить выходные операторы так, как нам хотелось бы.
А если вы копируете и вставляете код, то вас должны застрелить. Вы не хотите, чтобы вас подстрелили?
Чтобы решить эти проблемы и избежать выстрела, мы можем переписать этот блок кода с помощью функции обратного вызова. Каждый раз, когда встречается новый номер Фибоначчи, мы вызываем функцию обратного вызова.
void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t))
{
if(max-- == 0) return;
FoundNewFibCallback(0);
if(max-- == 0) return;
FoundNewFibCallback(1);
size_t pp = 0;
size_t p = 1;
for(;;)
{
if(max-- == 0) return;
int result = pp + p;
pp = p;
p = result;
FoundNewFibCallback(result);
}
}
void foundNewFib(size_t fibI)
{
std::cout << fibI << " ";
}
int main(int argc, char *argv[])
{
GetFibNumbers(6, foundNewFib);
return 0;
}
Это явно улучшение, ваша логика в main
не так загромождена, и вы можете делать все что угодно с числами Фибоначчи, просто определяя новые обратные вызовы.
Но это все еще не идеально. Что, если вы хотите получить только первые два числа Фибоначчи, а затем что-то сделать, затем получить еще что-нибудь, а затем сделать что-то еще?
Что ж, мы могли бы продолжать, как и раньше, и мы могли бы снова начать добавлять состояние в main
, позволяя GetFibNumbers запускаться с произвольной точки.
Но это еще больше раздувает наш код, и он уже выглядит слишком большим для такой простой задачи, как печать чисел Фибоначчи.
Мы могли бы реализовать модель производителя и потребителя через пару потоков. Но это еще больше усложняет код.
Вместо этого давайте поговорим о генераторах.
В Python есть очень хорошая языковая функция, которая решает проблемы, подобные этим, называемым генераторами.
Генератор позволяет вам выполнить функцию, остановиться в произвольной точке, а затем продолжить снова, где вы остановились.
Каждый раз возвращая значение.
Рассмотрим следующий код, который использует генератор:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Что дает нам результаты:
0
1
1
2
3
5
Оператор yield
используется совместно с генераторами Python. Сохраняет состояние функции и возвращает полученное значение. В следующий раз, когда вы вызовете функцию next () в генераторе, она продолжится там, где остановился выход.
Это намного более чисто, чем код функции обратного вызова. У нас более чистый код, меньший код, и не говоря уже о гораздо более функциональном коде (Python допускает произвольно большие целые числа).
Источник