Posting ini akan menggunakan angka Fibonacci sebagai alat untuk membangun guna menjelaskan kegunaan generator Python .
Posting ini akan menampilkan kode C ++ dan Python.
Angka-angka Fibonacci didefinisikan sebagai urutan: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Atau secara umum:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Ini dapat ditransfer ke fungsi C ++ dengan sangat mudah:
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);
}
Tetapi jika Anda ingin mencetak enam angka Fibonacci pertama, Anda akan menghitung ulang banyak nilai dengan fungsi di atas.
Misalnya :, Fib(3) = Fib(2) + Fib(1)
tetapi Fib(2)
juga menghitung ulang Fib(1)
. Semakin tinggi nilai yang ingin Anda hitung, Anda akan semakin buruk.
Jadi seseorang mungkin tergoda untuk menulis ulang di atas dengan melacak keadaan 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;
}
Tapi ini sangat jelek, dan mempersulit logika kita main
. Akan lebih baik untuk tidak perlu khawatir tentang keadaan di kitamain
fungsi .
Kita bisa mengembalikan a vector
nilai dan menggunakan nilai iterator
untuk mengulangi set nilai itu, tetapi ini membutuhkan banyak memori sekaligus untuk sejumlah besar nilai pengembalian.
Jadi kembali ke pendekatan lama kita, apa yang terjadi jika kita ingin melakukan hal lain selain mencetak angka? Kami harus menyalin dan menempelkan seluruh blok kodemain
dan mengubah pernyataan keluaran menjadi apa pun yang ingin kami lakukan. Dan jika Anda menyalin dan menempelkan kode, maka Anda harus ditembak. Anda tidak ingin tertembak, bukan?
Untuk mengatasi masalah ini, dan untuk menghindari tembakan, kami dapat menulis ulang blok kode ini menggunakan fungsi panggilan balik. Setiap kali nomor Fibonacci baru ditemukan, kami akan memanggil fungsi callback.
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;
}
Ini jelas merupakan peningkatan, logika Anda main
tidak berantakan, dan Anda dapat melakukan apa pun yang Anda inginkan dengan angka-angka Fibonacci, cukup tentukan panggilan balik baru.
Tapi ini masih belum sempurna. Bagaimana jika Anda hanya ingin mendapatkan dua angka Fibonacci pertama, lalu melakukan sesuatu, lalu mendapatkan lebih banyak, lalu melakukan sesuatu yang lain?
Yah, kita bisa terus seperti dulu, dan kita bisa mulai menambahkan status lagi main
, memungkinkan GetFibNumbers untuk memulai dari titik arbitrer. Tapi ini akan semakin menggembungkan kode kita, dan itu sudah terlihat terlalu besar untuk tugas sederhana seperti mencetak angka Fibonacci.
Kita dapat menerapkan model produsen dan konsumen melalui beberapa utas. Tetapi ini semakin menyulitkan kode.
Sebagai gantinya mari kita bicara tentang generator.
Python memiliki fitur bahasa yang sangat bagus yang memecahkan masalah seperti ini yang disebut generator.
Generator memungkinkan Anda untuk mengeksekusi suatu fungsi, berhenti pada titik arbitrer, dan kemudian melanjutkan lagi di mana Anda tinggalkan. Setiap kali mengembalikan nilai.
Pertimbangkan kode berikut yang menggunakan generator:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Yang memberi kami hasil:
0 1 1 2 3 5
Itu yield
pernyataan digunakan dalam conjuction dengan generator Python. Ini menyimpan status fungsi dan mengembalikan nilai yeilded. Lain kali Anda memanggil fungsi next () pada generator, ia akan melanjutkan di mana hasil ditinggalkan.
Ini jauh lebih bersih daripada kode fungsi panggilan balik. Kami memiliki kode yang lebih bersih, kode yang lebih kecil, dan belum lagi kode yang lebih fungsional (Python memungkinkan bilangan bulat besar secara sewenang-wenang).
Sumber
send
data ke generator. Setelah Anda melakukannya, Anda memiliki 'coroutine'. Sangat sederhana untuk menerapkan pola seperti Konsumen / Produsen yang disebutkan dengan coroutine karena mereka tidak memerlukanLock
s dan oleh karena itu tidak dapat menemui jalan buntu. Sulit untuk menggambarkan coroutine tanpa bashing thread, jadi saya akan mengatakan coroutine adalah alternatif yang sangat elegan untuk threading.