D'nin aralıklarıyla (ranges) oynamaya karar verdim. Kendi anladıklarımı hızlıca özetleyeceğim ve bir OutputRange örneği yazacağım.
Önce, D'de zaten 'baş .. son' şeklinde yazılan aralıklar bulunduğunu biliyoruz:
foreach (sayı; 0 .. 10) {
writeln(sayı);
}
O, çok basit olarak 'baş''tan 'son''a kadar ('son' dahil olmadan) bir aralık tanımlar.
Aslında dilimlerle de kullanılır ama o kullanımı bir aralık kabul edilmez; çünkü dilim kullanımında kendi kimliği bulunan bir aralık nesnesi değil, yalnızca bir değer aralığı belirler. O değer aralığı kullanılarak bir sonuç dilim oluşturulur ve 'baş .. son' kullanımının işi o noktada biter:
int[] dizi = [ 5, 6, 7, 8 ];
int[] yeniDizi = dizi[1 .. 3]; // dilim belirler ve amacı sona erer
assert(yeniDizi == [ 6, 7 ]);
D'nin aralıkları, o kavramlara benzerdir ama aslında oldukça farklıdır.
(Not: Aslında aralıklar dil olanağı değildir; Phobos kütüphanesinde gerçekleştirilmiştir. Yine de foreach ile yakından bağlantılı oldukları için ve aralık kavramını yayan ilk dil D olduğu için "D aralıkları" diyorum.)
Aralıklar birbirleriyle bir sıradüzendeymişler gibi ilgilidirler ama sınıfların getirdiği çalışma zamanı çokşekilliği değil, şablonların getirdiği derleme zamanı çokşekilliği üzerine kuruludurlar. Ucundan ilgili olan bu kavramlara şu derste değinmiştim:
http://ddili.org/ders/d/sablonlar_ayrintili.html
Oradaki "Derleme zamanı çok şekilliliği" başlığına bakabilirsiniz.
D'nin beş aralık çeşidi var. Bunlar std.range modülünün belgesinde var:
http://digitalmars.com/d/2.0/phobos/std_range.html
Orada özellikle şu tanımlara bakmak gerekiyor: isOutputRange, isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange.
Bu aralıkların kısa tanımları ve birbirleriyle ilişkileri şöyle:
OutputRange: Çıkış aralığı; çok kabaca, 'put' isminde ve yukarıdaki belgede anlatıldığı gibi işleyen bir üye işlevi bulunan her tür çıkış aralığı tanımına uyar
InputRange: Giriş aralığı; 'empty', 'popFront', ve 'front' işlevlerini tanımlayan her tür giriş aralığı tanımına uyar
ForwardRange: İlerleme aralığı; öncelikle InputRange'dir, ek olarak belirli bir andaki durumunu saklayabilir (yani kopyalanabilir)
BidirectionalRange: Çift yönlü ilerleme aralığı; öncelikle ForwardRange'dir; ek olarak, geriye doğru ilerlemeyi sağlayan 'back' ve 'popBack' işlevlerini de sunar
RandomAccessRange: Rasgele erişimli aralık; ya BidirectionalRange'dir veya sonsuz bir ForwardRange'dir; ek olarak 'opIndex' işlecini tanımlar; yine ek olarak ya uzunluğunu belirleyen 'length' işlevini tanımlar, ya da sonsuzdur
Phobos, aralıklarla ilgili olarak aşağıdaki olanakları da sunar:
ElementType: front'un döndürdüğü tür
hasMobileElements: elemanlarının sahipliği aktarılabilir mi
hasSwappableElements: elemanları değiş tokuş edilebilir mi
hasAssignableElements: elemanlarının değeri değiştirilebilir mi
hasLvalueElements: elemanları referans olarak geçirilebilirler mi ve adresleri var mıdır
hasLength: tamsayı türünde değer döndüren length işlevi var mı
isInfinite: statik olarak tanımlanmış 'enum bool empty = false' üyesi var mu
hasSlicing: InputRange döndüren bir [] işleci var mı
OutputRange örneğine geçmeden önce aralıkların ne işe yaradıklarına da çok kısaca değineyim. Aralıklar, algoritmaların üzerlerinde işleyebildikleri verileri soyutlamaya yararlar. Bunu anlamak için sıralı arama algoritmasını düşünelim. Sıralı aramayı dizilerle, bağlı listelerle, eşleme tablolarıyla, dizgilerle, dosyalarla, vs. kullanabiliriz. Güzel... Çok çeşitli veri üzerinde sırayla arayabiliyoruz. Peki, bu algoritmanın ne gerektirdiğini söyleyebiliriz? Dizilerle, bağlı listelerle, vs. mi çalışabilir?
Aralık kavramını uyguladığımızda sıralı aramanın giriş aralığı olan her türle çalışabildiğini görüyoruz. Bunu bir kere anladıktan sonra, sıralı aramayı InputRange ile işleyecek şekilde bir kere gerçekleştirmek yeter. Artık aynı algoritmayı InputRange olan her tür üzerinde kullanabiliriz. İşte o yüzden std.algorithm.find isInputRange koşulunu sağlayan her türle çalışabilir. Kendi türlerimizle bile...
std.algorithm.find'ın Türkçeleştirilmişi şöyle olabilir:
Aralık sıralıAra(alias koşul, Aralık)(Aralık aralık)
if (isInputRange!(Aralık))
{
alias unaryFun!(koşul) koşulİşlevi;
for (; !aralık.empty && !koşulİşlevi(aralık.front); aralık.popFront)
{
}
return aralık;
}
(Not: Oradaki unaryFun'ın aralıklarla ilgisi yok.)
OutputRange örneği
Bir çıkış aralığı kabul edilebilmek için 'put' üye işlevini tanımlamış olmak yeter. Kendi tanımlayacağımız böyle basit bir türü kullanmak için std.algorithm'de bulunan ve OutputRange gerektiren bir algoritmaya bakalım: 'copy', belirli bir giriş aralığındaki elemanları belirli bir çıkış aralığına kopyalar:
Range2 copy(Range1, Range2)(Range1 source, Range2 target)
if (isInputRange!Range1 && isOutputRange!(Range2, ElementType!Range1))
{
for (; !source.empty; source.popFront())
{
put(target, source.front);
}
return target;
}
Phobos belgeleri şablon koşullarını göstermediği için dmd/src/phobos/std/algorithm.d dosyasının içine bakmak zorunda kaldım ve yukarıdaki tanımı oradan kopyaladım.)
import std.stdio;
import std.algorithm;
class BizimÇıkışAralığı
{
int[] sayılar_;
void put(int sayı)
{
sayılar_ ~= sayı;
}
@property const (int[]) toplananlar() const
{
return sayılar_;
}
}
void main()
{
auto çıkış = new BizimÇıkışAralığı();
copy([ 100, 101, 102, 103 ], çıkış);
writeln(çıkış.toplananlar);
}
Oradaki sınıf, 'put' işlevini tanımladığı için bir OutputRange kabul edilir ve 'copy' ile kullanılabilir.
Biraz daha ilginçleştirmek için bizim çıkış aralığımıza seçici hale getirelim. En basit süzme kurallarından olduğu için % işlecini kullanarak tek sayıları seçmesini sağlayalım:
void put(int sayı)
{
if (sayı % 2) {
sayılar_ ~= sayı;
}
}
Programın çıktısı şimdi yalnızca tek sayıları içerir:
'[101, 103]'
Gördüğünüz gibi copy'nin bunlardan hiç haberi yok: o sayıları teker teker bizim çıkışa kopyalamaktadır.
Bir de kendimiz OutputRange kullanan bir algoritma yazalım: Bu algoritma, eksi değerli sayıları bir aralığa, artı değerlileri de başka aralığa göndersin.
Yukarıda çok kısaca değindiğim ve aşağıda kullandığım ElementType, kendisine verilen aralığın eleman türünü verir. O yüzden
isOutputRange!(HedefAralık, ElementType!KaynakAralık)
yazıldığında "HedefAralık, KaynakAralık'ın eleman türünü kullanan bir çıkış aralığı mıdır" denmiş oluyor.
import std.stdio;
import std.algorithm;
import std.range;
class BizimÇıkışAralığı
{
int[] sayılar_;
void put(int sayı)
{
if (sayı % 2) {
sayılar_ ~= sayı;
}
}
@property const (int[]) toplananlar() const
{
return sayılar_;
}
}
size_t eksiArtıAyrıştır(KaynakAralık, HedefAralık)(KaynakAralık kaynak,
HedefAralık eksiler,
HedefAralık artılar)
if (isInputRange!KaynakAralık &&
isOutputRange!(HedefAralık, ElementType!KaynakAralık))
{
size_t toplamSıfır;
for (; !kaynak.empty; kaynak.popFront())
{
if (kaynak.front < 0) {
put(eksiler, kaynak.front);
} else if (kaynak.front > 0) {
put(artılar, kaynak.front);
} else {
++toplamSıfır;
}
}
return toplamSıfır;
}
void main()
{
auto eksiler = new BizimÇıkışAralığı();
auto artılar = new BizimÇıkışAralığı();
auto toplamSıfır = eksiArtıAyrıştır([ 0, 7, -3, 2, 90, 0, -8, -5, 93 ],
eksiler, artılar);
writeln("eksiler: ", eksiler.toplananlar);
writeln("artılar: ", artılar.toplananlar);
writeln("toplam sıfır: ", toplamSıfır);
}
BizimÇıkışAralığı'nın yalnızca tek sayıları seçtiğini hatırlarsanız, programın çıktısı şöyle oluyor:
'eksiler: [-3, -5]
artılar: [7, 93]
toplam sıfır: 2
'
eksiArtıAyrıştır işlevi sıfırdan küçükleri ve büyükleri seçiyor, BizimÇıkışAralığı da tek sayılı olanları.
Yeni yazdığımız eksiArtıAyrıştır işlevini bir kere de dizilerle kullanmak istedim ama array.d içinde bir hata aldım:
import std.stdio;
import std.algorithm;
import std.range;
import std.array;
/* ... */
int[] eksiler;
int[] artılar;
eksiArtıAyrıştır([ 0, 7, -3, 2, 90, 0, -8, -5, 93 ],
appender(&eksiler), appender(&artılar));
writeln("eksiler: ", eksiler);
writeln("artılar: ", artılar);
Öyle yapınca hata alıyorum ama kodun yanlış olduğunu düşünmüyorum. :) Düzgün çalışması için şablon kısıtlamasını kaldırdım ama amacımıza tamamen ters! :)
size_t eksiArtıAyrıştır(KaynakAralık, HedefAralık)(KaynakAralık kaynak,
HedefAralık eksiler,
HedefAralık artılar)
// if (isInputRange!KaynakAralık &&
// isOutputRange!(HedefAralık, ElementType!KaynakAralık))
{
/* ... */
}
Ali
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]
Permalink
Reply