Aralıklarla (range) oynamaya devam ediyorum. En son ForwardRange'di:
http://ddili.org/forum/thread/425
Hızlıca hatırlıyorum:
OutputRange: çıkış aralığı; put() üye işlevi bulunan her sınıf (aslında tabii ki yapı da), bir çıkış aralığı olarak kullanılabilir
InputRange: giriş aralığı; empty(), front(), ve popFront() üye işlevleri bulunan her tür, giriş aralığı olarak kullanılabilir
ForwardRange: ilerleme aralığı; InputRange olmanın üstüne bir de save() işlevi bulunan her tür, ilerleme aralığı olarak kullanılabilir
Şimdi sıra BidirectionalRange'de:
BidirectionalRange: çift yönlü aralık (bence aslında İngilizce'de bile "iki uçlu aralık" denmeliymiş); ForwardRange olmanın üstüne back() ve popBack() üye işlevleri bulunan her tür, çift yönlü aralık olarak kullanılabilir
-
back(): aralığın en sonundaki elemanı verir
-
popBack(): aralığı sonundan (sağ tarafından) daraltır; "sondaki elemanı aralıktan çıkartır" da diyebiliriz (Not: asıl elemana bir şey olmak zorunda değildir; eleman, yalnızca bu aralığa dahil olmaktan çıkar.)
Sıra geldi mantıklı bir örnek bulmaya... Zor iş... :) Phobos modüllerine baktığımda BidirectionalRange kavramının genelde iki amaçla kullanıldığını görüyorum:
- Eğer algoritma BidirectionalRange ile daha hızlı çalışabilen bir algoritmaysa, o algoritmayı iki farklı şekilde yazıyorlar. Örneğin find'ın dmd/src/phobos/std/algorithm.d dosyasında iki tanımı var:
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isRandomAccessRange!R1 && isBidirectionalRange!R2
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
{
/* ... */
}
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
{
/* ... */
}
Konumuz dışında kalanlara karışmadan bu iki işlev şablonunun kısıtlamalarına bakalım: birincisi 'isBidirectionalRange!R2' diyor; yani "R2 bir BidirectionalRange ise". İkincisi ise '!isBidirectionalRange!R2' diyor; yani "R2 bir BidirectionalRange değilse".
find BidirectionalRange ile çağrıldığında birinci işlev kullanılıyor ve aralığın son elemanına da bakılabildiği için daha hızlı bir algoritma işletiliyor. Değilse ikinci tanımı kullanılıyor ve daha yavaş algoritma işletiliyor.
- Eğer bir sınıf şablonunun bir parametresi BidirectionalRange ise, o zaman sınıfa 'static if''ten yararlanarak bazı üyeler ekleyebiliyorlar. (Bunun bir örneğini aşağıda göstereceğim.)
Benim birinci örneğim şöyle olsun: verilen bir aralığın başındaki ve sonundaki elemanları bütün tekrarlarıyla dışarıda bırakan bir algoritma düşünelim. Örneğin "(((abc}}" verildiğinde başta tekrarlanan ( ve sonda tekrarlanan } karakterlerinin dışarıda kaldığı "abc" aralığını üretsin.
Sağ taraftaki elemana back() ile erişiriz ve popBack() ile çıkartırız:
import std.stdio;
import std.range;
Aralık uçtakileriAt(Aralık)(Aralık aralık)
if (isBidirectionalRange!Aralık)
{
if (aralık.empty) {
/* Boş aralığı olduğu gibi döndürüyoruz */
return aralık;
}
/* Baştakileri siliyoruz */
auto baştaki = aralık.front;
while (!aralık.empty && aralık.front == baştaki) {
aralık.popFront();
}
/* Sondakileri siliyoruz */
auto sondaki = aralık.back;
while (!aralık.empty() && aralık.back == sondaki) {
aralık.popBack();
}
return aralık;
}
void main()
{
writeln(uçtakileriAt("(((abc}}"));
}
Çıktısı:
'abc'
Kendisi BidirectionalRange olan bir sınıf örneği yazmaya gerek duymuyorum; çünkü şu işlevleri tanımlaması yeter:
i - empty()
i - front()
i - popFront()
f - save()
b - back()
b - popBack()
i: InputRange olarak kabul edilmesi için, f: ForwardRange olarak kabul edilmesi için, ve b: BidirectionalRange olarak kabul edilmesi için...
Son olarak yukarıdaki 2 numaralı maddede söylediğimin bir örneğini vermek istiyorum. Verilen aralıktaki değerlerin karelerini oluşturan bir aralık tanımlayalım:
import std.stdio;
import std.range;
/**
* Kendisine verilen aralıktaki değerlerin karelerini oluşturan bir aralık
* olarak işler
*
* Şimdilik bu sınıfı bir ForwardRange olarak tasarlayalım (bu kısıtlamayı
* daha sonra gidereceğiz)
*/
class KareAlıcı(Aralık)
if (isForwardRange!Aralık)
{
Aralık aralık;
this(Aralık aralık)
{
/* Kurucu işleve verilen aralığa hiç karışmamak için onun bir kopyası
* ile çalışalım. Sınıf şablonunun tanımında isForwardRange!Aralık
* kısıtlaması getirmiş olduğumuz için bunun bir ForwardRange
* olduğundan eminiz. Onun save() işlevi bulunduğunu biliyoruz. */
this.aralık = aralık.save();
}
@property bool empty()
{
/* Özel bir şey yapmaya gerek yok; elimizdeki aralığın boş olup olmaması
* bizim de boş olup olmadığımızı belirliyor */
return aralık.empty;
}
@property ElementType!Aralık front()
{
/* İşte bizim asıl işimiz: verilen aralıktaki değerlerin karelerini
* oluşturmak */
return aralık.front * aralık.front;
}
void popFront()
{
/* Yapacak özel bir şey yok */
aralık.popFront();
}
KareAlıcı!Aralık save()
{
/* Aşağıdaki kolaylık işlevinden yararlanıyoruz; nasıl olsa o kurucu
* işlevimizi çağırıyor ve onun içinde .save() ile yeni bir kopya
* alınıyor */
return kareAlıcı(aralık);
}
}
/* Kolaylık işlevi: kullanıcı kodu 'new' yazmak ve şablon parametresi
* belirtmek zorunda kalmasın diye kullanışlı */
KareAlıcı!Aralık kareAlıcı(Aralık)(Aralık aralık)
{
return new KareAlıcı!Aralık(aralık);
}
Görüldüğü gibi onu bir ForwardRange olarak tanımladık. Önceki konuda bir ForwardRange olarak tanımladığımız SonsuzaKadarSayan sınıfı ile deneyelim:
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ç);
}
}
SonsuzaKadarSayan sonsuzSayaç(int başlangıç = 0)
{
return new SonsuzaKadarSayan(başlangıç);
}
void main()
{
writeln(kareAlıcı(take(sonsuzSayaç(3), 5)));
}
main'in içindeki satırın anlamı: "3'ten başlayan sonsuz aralığın ilk 5 elemanını seç ve onların karelerini oluştur". Çıktısı:
'[9, 16, 25, 36, 49]'
Bu kadarı güzel... Ancak, bize verilen asıl aralık kendisi bir BidirectionalRange olduğunda bizim tanımlamış olduğumuz KareAlıcı'nın da BidirectionalRange olarak çalışamaması için bir neden yoktur. Eğer verilen aralık BidirectionalRange ise, biz de kolaylıkla back() ve popBack() işlevlerini tanımlayabiliriz.
Bunun için KareAlıcı şablonunun kısıtlamalarını biraz esnekleştiriyoruz ve sınıfa duruma bağlı olarak back() ve popBack() üye işlevleri ekliyoruz:
class KareAlıcı(Aralık)
if (isForwardRange!Aralık)
{
/* ... diğer üyeleri eskisiyle aynı ... */
static if (isBidirectionalRange!Aralık) {
@property ElementType!Aralık back()
{
/* Yine işimiz, sondaki elemanın da karesini oluşturmaktır */
return aralık.back * aralık.back;
}
void popBack()
{
/* Yine çok basitçe */
aralık.popBack();
}
}
}
'static if' derleme zamanında işletilir ve asıl aralık bir BidirectionalRange olduğunda KareAlıcı şablonuna iki üye işlev daha eklenmiş olur. Asıl aralık yalnızca bir ForwardRange olduğunda ise bu iki üye işlev bulunmaz ve .back() veya .popBack() işlevlerini kullanmaya çalışmak derleme hatasına neden olur.
Bütün program:
import std.stdio;
import std.range;
/**
* Kendisine verilen aralıktaki değerlerin karelerini oluşturan bir aralık
* olarak işler
*
* Şimdilik bu sınıfı bir ForwardRange olarak tasarlayalım (bu kısıtlamayı
* daha sonra gidereceğiz)
*/
class KareAlıcı(Aralık)
if (isForwardRange!Aralık)
{
Aralık aralık;
this(Aralık aralık)
{
/* Kurucu işleve verilen aralığa hiç karışmamak için onun bir kopyası
* ile çalışalım. Sınıf şablonunun tanımında isForwardRange!Aralık
* kısıtlaması getirmiş olduğumuz için bunun bir ForwardRange
* olduğundan eminiz. Onun save() işlevi bulunduğunu biliyoruz. */
this.aralık = aralık.save();
}
@property bool empty()
{
/* Özel bir şey yapmaya gerek yok; elimizdeki aralığın boş olup olmaması
* bizim de boş olup olmadığımızı belirliyor */
return aralık.empty;
}
@property ElementType!Aralık front()
{
/* İşte bizim asıl işimiz: verilen aralıktaki değerlerin karelerini
* oluşturmak */
return aralık.front * aralık.front;
}
void popFront()
{
/* Yapacak özel bir şey yok */
aralık.popFront();
}
KareAlıcı!Aralık save()
{
/* Aşağıdaki kolaylık işlevinden yararlanıyoruz; nasıl olsa o kurucu
* işlevimizi çağırıyor ve onun içinde .save() ile yeni bir kopya
* alınıyor */
return kareAlıcı(aralık);
}
static if (isBidirectionalRange!Aralık) {
@property ElementType!Aralık back()
{
/* Yine işimiz, sondaki elemanın da karesini oluşturmaktır */
return aralık.back * aralık.back;
}
void popBack()
{
/* Yine çok basitçe */
aralık.popBack();
}
}
}
/* Kolaylık işlevi: kullanıcı kodu 'new' yazmak ve şablon parametresi
* belirtmek zorunda kalmasın diye kullanışlı */
KareAlıcı!Aralık kareAlıcı(Aralık)(Aralık aralık)
{
return new KareAlıcı!Aralık(aralık);
}
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ç);
}
}
SonsuzaKadarSayan sonsuzSayaç(int başlangıç = 0)
{
return new SonsuzaKadarSayan(başlangıç);
}
void main()
{
writeln(`"3'ten başlayan sonsuz aralığın ilk 5 elemanını seç`);
writeln(`ve onların karelerini oluştur"`);
writeln(kareAlıcı(take(sonsuzSayaç(3), 5)));
writeln();
auto sayılar = [13, 7, 8, 9, 13, 13];
writeln("Asıl sayılar: ", sayılar);
auto kareci = kareAlıcı(sayılar);
writeln("KareAlıcı aralığının sonundaki değer: ", kareci.back);
writeln("Baştan bir tane ve sondan iki tane çıkarttıktan sonraki aralık");
kareci.popFront();
kareci.popBack();
kareci.popBack();
writeln(kareci);
}
Çıktısı:
'"3'ten başlayan sonsuz aralığın ilk 5 elemanını seç
ve onların karelerini oluştur"
[9, 16, 25, 36, 49]
Asıl sayılar: [13, 7, 8, 9, 13, 13]
KareAlıcı aralığının sonundaki değer: 169
Baştan bir tane ve sondan iki tane çıkarttıktan sonraki aralık
[49, 64, 81]
'
Devamı gelecek... :)
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]