Thread overview
BidirectionalRange örneği
Mar 04, 2012
Salih Dinçer
Mar 04, 2012
Salih Dinçer
Mar 05, 2012
Salih Dinçer
December 23, 2010

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:

  1. 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.

  1. 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. ]

March 04, 2012

Bu aralıklar muazzam derecede güzel bir konu olduğunu düşünüyorum. Sabahlara kadar çalışsak veya bütün bir hafta sonunu buna ayırsak herhalde yeni bir haftaya "Aralıkları biliyorum!"* diye başlayamayız...:)

(*) The Matrix'e gönderme: I know Kung-fu...

Küçük bir şeyi anlamıyorum: Sonsuza kadar sayan bir aralık oluşturduğumuzda, örneğin fib(7_000_000) dediğimizde; biz verdiğimiz değere kadar hesaplatmayıp (işlemci gücünü idareli kullanarak) kuramsal (theory) açıdan ilgili aralılığı alıp yansıtmak mümkün mü?

Komik bir soru oldu ama kusura bakmayın...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 05, 2012

Sonsuzluk çelişkisine takılan hatalı ifadem şu şekilde:

'struct' FibonacciSerisi { '...' } yapısı sonsuz ise,
Alıntı:

>
> struct SınırlıFibonacciSerisi {
>     int sınır, öndeki;
>     FibonacciSerisi seri;
>
>     this(int sınır) {
> ```

> ': : :'
>

Yukarıdaki gibi başka bir yapı ile sınırlandırsak;

Aslında kuramsal (theory) olarak baştan itibaren hesaplatıp 7 milyonuncu Fibonacci sayısını göstermek için yukarıdan (7 milyonun üstünü) kesmiş mi olmaktayız? En iyisi şu yeni kodu deneyim...

Teşekkür etmeliyim, teşekkürler...:)

-- 
[ Bu gönderi, <http://ddili.org/forum>'dan dönüştürülmüştür. ]
March 05, 2012

Alıntı (Salih Dinçer):

>

Bu aralıklar muazzam derecede güzel bir konu olduğunu düşünüyorum.

Evet, yepyeni bir pencere açıyorlar. :)

Alıntı:

>

Sabahlara kadar çalışsak veya bütün bir hafta sonunu buna ayırsak herhalde yeni bir haftaya "Aralıkları biliyorum!"* diye başlayamayız...:)

Aslında olabilir çünkü temelde şaşırtıcı derecede basit bir kavram. En basitinden empty, front, popFront() üçlüsünü anlamak olayı büyük ölçüde hallediyor. Şunu okuyorsun değil mi:

http://ddili.org/ders/d/araliklar.html

Alıntı:

>

Küçük bir şeyi anlamıyorum: Sonsuza kadar sayan bir aralık oluşturduğumuzda, örneğin fib(7_000_000) dediğimizde;

Çelişki oldu: Sonsuza kadar sayan kısmını anlıyorum ama 7_000_000 dediğin zaman sonsuz olmuyor.

Alıntı:

>

biz verdiğimiz değere kadar hesaplatmayıp (işlemci gücünü idareli kullanarak) kuramsal (theory) açıdan ilgili aralılığı alıp yansıtmak mümkün mü?

Tembellik kavramını mı soruyorsun? Aşağıdaki program sonsuz olan FibonacciSerisi aralığını başka bir aralığa veriyor. Yalnızca 10 kere popFront() dediği için de işlemci popFront()'lar dışında iş yapmıyor.

import std.stdio;
import std.range;

struct FibonacciSerisi
{
   int baştaki = 0;
   int sonraki = 1;

   enum empty = false;

   @property int front() const
   {
       return baştaki;
   }

   void popFront()
   {
       int ikiSonraki = baştaki + sonraki;
       baştaki = sonraki;
       sonraki = ikiSonraki;
   }
}

struct Katlayıcı(Aralık)
{
   Aralık aralık;
   int çarpan;

   this(int çarpan)
   {
       this.çarpan = çarpan;
   }

   bool empty() const @property
   {
       return aralık.empty;
   }

   ElementType!Aralık front() const @property
   {
       return aralık.front * çarpan;
   }

   void popFront()
   {
       aralık.popFront();
   }
}

Katlayıcı!Aralık katlayıcı(Aralık)(Aralık aralık, int çarpan)
{
   return Katlayıcı!Aralık(çarpan);
}

void main()
{
   auto f = FibonacciSerisi();
   auto ka = katlayıcı(f, 2);

   foreach (i; 0 .. 10) {
       writeln(ka.front);
       ka.popFront();
   }
}

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 06, 2012

Kodu az önce deneme fırsatı buldum. Standarda fonksiyonun iki katı, yani;
'fib(0) = 0 * 2,
fib(1) = 1 * 2,
fib(2) = 1 * 2...'

Olduğunu gördüm ama hala aralıkları tam anlayamıyorum...:)

Bana sihirli görünüyorlar ama belki de büyütülecek hiç bir yanı yoktur.

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]