Bu yazı , Python jeneratörlerinin kullanışlılığını açıklamak için bir araç olarak Fibonacci sayılarını kullanacaktır .
Bu yazı hem C ++ hem de Python koduna sahip olacak.
Fibonacci sayıları dizi olarak tanımlanır: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Veya genel olarak:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Bu son derece kolay bir C ++ fonksiyonuna aktarılabilir:
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);
}
Ancak ilk altı Fibonacci numarasını yazdırmak istiyorsanız, yukarıdaki işlevle birçok değeri yeniden hesaplayacaksınız.
Örneğin:, Fib(3) = Fib(2) + Fib(1)
aynı Fib(2)
zamanda yeniden hesaplar Fib(1)
. Hesaplamak istediğiniz değer ne kadar yüksek olursa, o kadar kötü olur.
Dolayısıyla, durumu takip ederek yukarıdakileri yeniden yazmak cazip gelebilir 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;
}
Ama bu çok çirkin ve mantığımızı zorlaştırıyor main
. Bizim durumumuzda devlet hakkında endişelenmemek daha iyi olurdumain
.
Bir vector
değer döndürebilir ve iterator
bu değerler kümesi üzerinde yineleme yapmak için a kullanabiliriz , ancak bu, çok sayıda dönüş değeri için bir kerede çok fazla bellek gerektirir.
Eski yaklaşımımıza geri dönersek, sayıları yazdırmanın yanı sıra başka bir şey yapmak istersek ne olur? Tüm kod bloğunu kopyalayıp yapıştırmamız main
ve çıktı ifadelerini başka ne yapmak istiyorsak değiştirmeliyiz. Ve kodu kopyalayıp yapıştırırsanız, vurulmalısınız. Vurulmak istemiyorsun, değil mi?
Bu sorunları çözmek ve vurulmaktan kaçınmak için, geri arama işlevini kullanarak bu kod bloğunu yeniden yazabiliriz. Her yeni Fibonacci numarasıyla karşılaşıldığında, geri arama fonksiyonunu çağırırdık.
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;
}
Bu açıkça bir gelişme, mantığınız main
o kadar karmaşık değil ve Fibonacci numaraları ile istediğiniz her şeyi yapabilirsiniz, sadece yeni geri aramalar tanımlayın.
Ama bu hala mükemmel değil. Ya sadece ilk iki Fibonacci numarasını almak ve sonra bir şey yapmak, sonra biraz daha almak, sonra başka bir şey yapmak istersen?
main
Olduğu gibi devam edebiliriz ve GetFibNumbers'ın keyfi bir noktadan başlamasına izin vererek tekrar devlet eklemeye başlayabiliriz. Ancak bu, kodumuzu daha da şişirir ve Fibonacci sayılarını yazdırmak gibi basit bir görev için zaten çok büyük görünüyor.
Birkaç iş parçacığıyla üretici ve tüketici modeli uygulayabiliriz. Ancak bu, kodu daha da karmaşık hale getirir.
Bunun yerine jeneratörler hakkında konuşalım.
Python, jeneratör denilen gibi sorunları çözen çok güzel bir dil özelliğine sahiptir.
Jeneratör, bir işlevi yürütmenize, rastgele bir noktada durmanıza ve daha sonra kaldığınız yerden devam etmenize olanak tanır. Her seferinde bir değer döndürür.
Bir jeneratör kullanan aşağıdaki kodu göz önünde bulundurun:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Bu da bize sonuçları verir:
0 1 1 2 3 5
Bu yield
ifade Python jeneratörleri ile birlikte kullanılır. Fonksiyonun durumunu kaydeder ve sarılık değeri döndürür. Jeneratörde sonraki () fonksiyonunu bir sonraki çağırışınızda, verimin kaldığı yerden devam eder.
Bu, geri arama işlev kodundan çok daha temizdir. Daha temiz kod, daha küçük kod var ve çok daha fonksiyonel koddan bahsetmiyoruz (Python keyfi olarak büyük tamsayılara izin veriyor).
Kaynak
send
Bir jeneratöre veri vermenin mümkün olduğunu belirtiyorsunuz . Bunu yaptıktan sonra bir 'coroutine' var. Bahsedilen Tüketici / Üretici gibi kalıpları koroutinlerle uygulamak çok basittir çünküLock
s'ye ihtiyaç duymazlar ve bu nedenle kilitlenemezler. Koroutinleri dişleri dayamadan tanımlamak zor, bu yüzden koroutinlerin diş açmaya çok zarif bir alternatif olduğunu söyleyeceğim.