Bài đăng này sẽ sử dụng các số Fibonacci như một công cụ để xây dựng để giải thích tính hữu ích của các trình tạo Python .
Bài đăng này sẽ có cả mã C ++ và Python.
Các số Fibonacci được định nghĩa là chuỗi: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Hoặc nói chung:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Điều này có thể được chuyển vào một chức năng C ++ cực kỳ dễ dàng:
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);
}
Nhưng nếu bạn muốn in sáu số Fibonacci đầu tiên, bạn sẽ tính toán lại rất nhiều giá trị với hàm trên.
Ví dụ : Fib(3) = Fib(2) + Fib(1)
, nhưng Fib(2)
cũng tính toán lại Fib(1)
. Giá trị bạn muốn tính toán càng cao, bạn sẽ càng tệ.
Vì vậy, người ta có thể bị cám dỗ để viết lại ở trên bằng cách theo dõi trạng thái trong 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;
}
Nhưng điều này rất xấu, và nó làm phức tạp logic của chúng tôi main
. Sẽ tốt hơn nếu không phải lo lắng về trạng thái trong main
chức năng của chúng tôi .
Chúng ta có thể trả về một vector
giá trị và sử dụng iterator
để lặp lại tập hợp các giá trị đó, nhưng điều này đòi hỏi rất nhiều bộ nhớ cùng một lúc cho một số lượng lớn các giá trị trả về.
Vì vậy, trở lại với cách tiếp cận cũ của chúng tôi, điều gì xảy ra nếu chúng tôi muốn làm một cái gì đó khác ngoài việc in các con số? Chúng tôi phải sao chép và dán toàn bộ khối mã vào main
và thay đổi các câu lệnh đầu ra thành bất cứ điều gì khác mà chúng tôi muốn làm. Và nếu bạn sao chép và dán mã, thì bạn nên bị bắn. Bạn không muốn bị bắn, phải không?
Để giải quyết những vấn đề này và để tránh bị bắn, chúng tôi có thể viết lại khối mã này bằng chức năng gọi lại. Mỗi khi gặp phải một số Fibonacci mới, chúng ta sẽ gọi hàm gọi lại.
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;
}
Đây rõ ràng là một sự cải tiến, logic của bạn main
không bị lộn xộn và bạn có thể làm bất cứ điều gì bạn muốn với các số Fibonacci, chỉ cần xác định các cuộc gọi lại mới.
Nhưng điều này vẫn chưa hoàn hảo. Điều gì sẽ xảy ra nếu bạn muốn chỉ nhận được hai số Fibonacci đầu tiên, và sau đó làm một cái gì đó, sau đó nhận thêm một số, sau đó làm một cái gì đó khác?
Chà, chúng ta có thể tiếp tục như chúng ta đã từng và chúng ta có thể bắt đầu thêm trạng thái trở lại main
, cho phép GetFibNumbers bắt đầu từ một điểm tùy ý. Nhưng điều này sẽ làm tăng thêm mã của chúng tôi và nó có vẻ quá lớn đối với một tác vụ đơn giản như in các số Fibonacci.
Chúng tôi có thể thực hiện một mô hình sản xuất và tiêu dùng thông qua một vài chủ đề. Nhưng điều này làm phức tạp mã hơn nữa.
Thay vào đó hãy nói về máy phát điện.
Python có một tính năng ngôn ngữ rất hay giúp giải quyết các vấn đề như những máy phát điện này.
Một trình tạo cho phép bạn thực thi một chức năng, dừng tại một điểm tùy ý và sau đó tiếp tục lại nơi bạn rời đi. Mỗi lần trả lại một giá trị.
Hãy xem xét đoạn mã sau sử dụng trình tạo:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Cung cấp cho chúng tôi kết quả:
0 1 1 2 3 5
Câu yield
lệnh được sử dụng kết hợp với trình tạo Python. Nó lưu trạng thái của hàm và trả về giá trị yeilded. Lần sau khi bạn gọi hàm next () trên trình tạo, nó sẽ tiếp tục ở nơi sản lượng còn lại.
Điều này là sạch hơn nhiều so với mã chức năng gọi lại. Chúng tôi có mã sạch hơn, mã nhỏ hơn và chưa kể mã chức năng nhiều hơn (Python cho phép số nguyên lớn tùy ý).
Nguồn
send
dữ liệu cho một máy phát điện. Một khi bạn làm điều đó, bạn có một 'coroutine'. Rất đơn giản để thực hiện các mẫu như Người tiêu dùng / Nhà sản xuất được đề cập với coroutines vì họ không có nhu cầu vềLock
s và do đó không thể bế tắc. Thật khó để mô tả coroutines mà không bash thread, vì vậy tôi sẽ chỉ nói coroutines là một thay thế rất thanh lịch cho luồng.