Этот пост будет использовать числа Фибоначчи как инструмент для объяснения полезности генераторов 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
значения a и использовать 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 допускает произвольно большие целые числа).
Источник
send
данных к генератору. Как только вы это сделаете, у вас будет «сопрограмма». Реализовать такие шаблоны, как упомянутый Consumer / Producer, с сопрограммами очень просто, потому что им не нужныLock
s и, следовательно, они не могут зайти в тупик. Трудно описать сопрограммы без разбивки потоков, поэтому я просто скажу, что сопрограммы - очень элегантная альтернатива многопоточности.