У цій публікації будуть використані числа Фібоначчі як інструмент для пояснення корисності генераторів 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. Це зберігає стан функції та повертає зведене значення. Наступного разу, коли ви зателефонуєте на наступну () функцію на генераторі, вона продовжиться там, де вихід відключений.
Це набагато більш чисто, ніж код функції зворотного дзвінка. У нас є чистіший код, менший код, не кажучи вже про набагато більш функціональний код (Python дозволяє довільно великі цілі числа).
Джерело
send
передавати дані в генератор. Як тільки ви це зробите, у вас є "співавтор". Реалізувати такі моделі, як згаданий споживач / виробник, дуже просто за допомогою процедур, оскільки вони не потребуютьLock
s, а тому не можуть зайти в тупик. Важко описати співпрограми без розбиття ниток, тому я просто скажу, що супроводи є дуже елегантною альтернативою нитці.