Daha önce bir InputRange örneği vermiştim:
http://ddili.org/forum/thread/424
Şimdi de InputRange'lerden tek bir üstünlüğü bulunan ForwardRange'lerin bir örneğini vereceğim. (Bunlar hep kendim denerken aldığım notlar.)
Açıklaması çok kolay: Ben ForwardRange'e şimdilik "ilerleme aralığı" diyorum. ForwardRange de aslında bir InputRange'dir. InputRange'in sunduğu işlevleri (empty, front, ve popFront) aynen sunar. Ek olarak, aralığın belirli bir andaki durumunu saklamaya yarayan 'save' işlevi de vardır.
Bu ikisinin farkını tersten düşünmek daha mantıklı: ForwardRange, ileri yönde ilerleyerek gezilen bir aralıktır. InputRange ise belirli bir andaki halini saklama yeteneği bulunmayan bir ForwardRange gibi düşünülebilir. InputRange, kullanıldıkça veri kaybeder diye de düşünebiliriz; veya "yerini kaybeder" diyebilir.
Örneğin stdin bir InputRange olarak düşünülebilir; çünkü stdin'den okunan karakterler artık stdin'den çıkartılmış olurlar. stdin'e "bu halini sakla; bu noktadan itibaren sonra tekrar kullanacağım" diyemiyoruz.
Özet: ForwardRange, aralığın belirli bir andaki durumunu saklamaya yarayan save() işlevi de olan bir InputRange'dir.
Önceki konuda geçen SonsuzaKadarSayan aralığı bir InputRange gibi işliyordu:
class SonsuzaKadarSayan
{
int sayaç;
static immutable bool empty = false;
@property int front() const
{
return sayaç;
}
void popFront()
{
++sayaç;
}
}
Onu bir ForwardRange haline getirmek mümkündür ve oldukça kolaydır; çünkü belirli bir noktadan itibaren tekrar devam edebilmesi için sayaç'ın değerini saklaması yeter:
SonsuzaKadarSayan save() const
{
return /* burada aralığın şu andaki halini saklayan yeni bir
* SonsuzaKadarSayan nesnesi oluşturmamız gerekir */;
}
Yukarıdaki save işlevi bu nesnenin sayaç değerine sahip olan yeni bir SonsuzaKadarSayan nesnesi döndürmelidir. Ama dikkat ederseniz SonsuzaKadarSayan'ın içindeki sayaç hep int'in ilk değeri olan 0'dan başlamakta... İşte bunu değiştirmemiz gerekiyor. Bunu becerebilmek için SonsuzaKadarSayan'ın kurucu işlevini hangi değerden başlayacağını alacak şekilde değiştirmemiz gerekir. Başlangıcın varsayılan değerini yine 0 yapabiliriz:
class SonsuzaKadarSayan
{
int sayaç;
this(int başlangıç = 0)
{
sayaç = başlangıç;
}
static immutable bool empty = false;
@property int front() const
{
return sayaç;
}
void popFront()
{
++sayaç;
}
SonsuzaKadarSayan save() const
{
return new SonsuzaKadarSayan(sayaç);
}
}
Artık başlangıç değerini verebildiğimiz bir kurucu işlevi var; ve varsayılan değeri de 0 olduğu için alışılmış davranışını da değiştirmemiş olduk. save işlevi de şu andaki nesnenin sayaç değerini kullanarak yeni bir SonsuzaKadarSayan nesnesi oluşturuyor.
sonsuzSayaç() isimli ve yazım kolaylığı amacıyla yazılmış olan işlevimizi de bu başlangıç değerini aktaracak şekilde değiştirirsek şöyle bir programda deneyebiliriz:
import std.stdio;
import std.range;
class SonsuzaKadarSayan
{
int sayaç;
this(int başlangıç = 0)
{
sayaç = başlangıç;
}
static immutable bool empty = false;
@property int front() const
{
return sayaç;
}
void popFront()
{
++sayaç;
}
SonsuzaKadarSayan save() const
{
return new SonsuzaKadarSayan(sayaç);
}
}
/* Kullanıcı kodu hep 'new' yazmak zorunda kalmasın diye bir kolaylık işlevi */
SonsuzaKadarSayan sonsuzSayaç(int başlangıç = 0)
{
return new SonsuzaKadarSayan(başlangıç);
}
void main()
{
auto aralık = sonsuzSayaç();
/* Aralığın ilk 3 elemanını burada kullanıyoruz */
writeln("Asıl aralıktan 3 eleman : ", take(aralık, 3));
/* Bu halini bir kenara kaydediyoruz */
auto kaydedilen = aralık.save();
writeln("Asıl aralıktan 2 tane daha : ", take(aralık, 2));
writeln("Kaydedilen aralıktan 5 tane: ", take(kaydedilen, 5));
}
Çıktısı:
'Asıl aralıktan 3 eleman : [0, 1, 2]
Asıl aralıktan 2 tane daha : [3, 4]
Kaydedilen aralıktan 5 tane: [3, 4, 5, 6, 7]
'
Gördüğünüz gibi save()'in döndürdüğü nesne kaydedildiği noktadan devam edebilmektedir.
Şimdi bunu daha mantıklı bir algoritmada kullanalım: "Verilen bir aralık içinde art arda belirli sayıda tekrarlanan elemanı bulunuz ve o elemandan başlayan bir aralık döndürünüz."
Örneğin [ 0, 0, 1, 2, 2, 2, 3 ] aralığında 3 kere tekrarlanan eleman istendiğinde, üç kere tekrarlandığını gördüğümüz 2'den başlayan [ 2, 2, 2, 3 ] aralığını elde etmek istiyoruz.
Bu işi bir InputRange ile halledemeyiz; çünkü art arda olup olmadıklarını anlamak için ilerledikçe belki de döndürmemiz gereken baş tarafını artık elimizden kaçırmış oluruz. Yani 2'lerin ilkini atladıktan sonra bir daha ona geri dönemeyiz.
Bu işlev herhalde daha temiz bir şekilde yazılabilirdi ve belki de hataları da vardır ama şöyle bir şey oldu:
import std.stdio;
import std.range;
import std.array;
Aralık tekrarlananıBul(Aralık)(Aralık aralık, int tekrarSayısı)
if (isForwardRange!Aralık)
{
/* Bu halini kaydedelim; belki de sonuç olarak bunu döndüreceğiz */
Aralık aday = aralık.save();
int sayaç = 0;
for ( ; !aralık.empty; aralık.popFront()) {
if (aralık.front == aday.front) {
/* Tekrarlanıyor */
++sayaç;
if (sayaç == tekrarSayısı) {
/* Yeterince tekrarlanmış; bulduk */
return aday;
}
} else {
/* Başka bir elemana geçtik; aday olarak düşündüğümüz aralığı
* değiştirelim */
aday = aralık.save();
sayaç = 1;
}
}
/* Buraya ancak aralık boşaldığında geleceğiz; denetleyelim */
assert(aralık.empty);
/* Bulamadığımızı bildirmek için bu boş aralığın kopyasını döndürelim */
return aralık.save();
}
unittest
{
assert(tekrarlananıBul([ 1 ], 1) == [ 1 ]);
assert(tekrarlananıBul([ 1, 2, 2, 3 ], 1) == [ 1, 2, 2, 3 ]);
assert(tekrarlananıBul([ 1, 2, 2, 3 ], 2) == [ 2, 2, 3 ]);
assert(tekrarlananıBul([ 1, 2, 2, 3, 3, 3 ], 3) == [ 3, 3, 3 ]);
}
Geldik güzel tarafına... :) Herhangi bir aralıkla kullanabiliriz; örneğin bir dizgiyle:
writeln(tekrarlananıBul("abccççdefgğğğhıi", 3));
Çıktısı, üç kere tekrarlanan elemanla başlayan aralıktır:
'ğğğhıi'
take'in bir yararını bu durumda görüyoruz. Eğer take'i kullanırsak daha mantıklı olarak yalnızca üç kere tekrarlanan bölümü de elde edebiliriz:
writeln(take(tekrarlananıBul("abccççdefgğğğhıi", 3), 3));
Çıktısı:
'ğğğ'
Tabii çoğu zaman öyle bir eleman bulunup bulunmadığını ve ne olduğunu bilmek isteriz:
auto sonuç = tekrarlananıBul("abccççdefgğğğhıi", 3);
if (!sonuç.empty) {
writeln("Üç kere tekrarlanan harf: ", sonuç.front);
} else {
writeln("Yokmuş");
}
Çıktısı:
'Üç kere tekrarlanan harf: ğ'
Devamı gelecek... :)
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]
Permalink
Reply