Thread overview
Aralıklar (ranges) ile ilgili bir sorunu çözmek
April 04

Bu bir yazı dizisi olacak...

Aslında başlığı pekala "Aralıklarla Problem Çözme" olarak değiştirebiliriz çünkü zat-ı muhterem kendileri, matematiksel olsun olmasın her sorunu çözer diyebilirim ya da şöyle iddialı başlık atalım:

D Programlama Dilinde Aralıklar:

D dediğimizde aklımıza diziler (dolayısıyla dilimler) ve adeta iç içe geçmiş aralıklar (ranges) gelmelidir. Çünkü kütüphane olanağı olan std.array, std.range ve bunlar üzerinde akrobatlık yapabileceğiniz std.algorithm modülleri vardır.

Ve bu bir ders değildir, ders için Ali Çehreli'nin kitabındaki şu bölümleri okuyabilirsiniz:

Son olarak lütfen bu grubun (uluslararası resmi D forumunun tek alternatif dili olan Türkçe bölümün) şahsımın bloğu gibi görünmesine izin vermeyin! Aralarda hiç bir sorunuz yoksa bile "merhaba, zevkle okuyorum ve devamını bekliyorum!" yaz ve gönder gitsin bea ya...:)

import std.algorithm, std.range;

void main()
{
  auto dizi = [0, 1, 2, 3, 4, 5, 6, 7, 8];
  auto aralık = iota(9);
  assert(aralık.equal(dizi));

  import std.array;

  auto artıkDizi = aralık.array;
  assert(artıkDizi == dizi);

//...

Önce yukardaki temel konuyla başlayalım ve hemen arkasında bitirmek istediğim noktaya değineceğim. Yani yazı dizisi bittiğinde ulaşacağımız bilgi birikimi bu olacak. Lütfen hafife almayın, çünkü ben 10 senede ancak anladım!

Yukardaki satırlar kendilerini açıklıyor, değil mi? Peki bu sayıları, 0 hariç ikili parçalar (chunks) halinde toplayıp ekrana yazmak isteseydik ne yapacaktık?

  import std.stdio;

  aralık.dropOne                // baştaki 0'ı geçer: aralıkta 1 adım ilerler
        .chunks(2)              // artık böyle: [[1, 2], [3, 4], [5, 6], [7, 8]]
        .map!"a.front + a.back" // ama dikkat, bu bir dizi değil
        .writeln;               // [3, 7, 11, 15]
}

Bu bölümde kısa biri giriş yapmak istedim. Gönderir göndermez hemen 2. bölümü yazacağım ve muhtemelen siz bu satırları okuyorken zaten aşağıda şunlar olacak:

  1. aslında iota(9) ne demek, bunu bize pragma gösterecek
  2. en basit aralık (InputRange) nedir ve
  3. SonsuzAralık ve kendi _iota() işlevimizi yazalım.
April 04

On Thursday, 4 April 2024 at 04:48:42 UTC, Salih Dincer wrote:

>
  1. aslında iota(9) ne demek, bunu bize pragma gösterecek
  2. en basit aralık (InputRange) nedir ve
  3. SonsuzAralık ve kendi _iota() işlevimizi yazalım.

Şimdi yazının temelini oluşturan uzun bir kod ile devam ediyoruz. Aslında main()'deki başlangıcı hali hazırda yukarda işledik (1. bölümdü) ve devamına baktığımızda take() şablonunu kullanarak aslında hiç bir şeyi sınırlandırmadan sonsuza giden bir aralık tanımlamanın ne kadar basit olduğunu bölüm 2a ve 2b'de göreceğiz.

Bu kod ürkütmesin çünkü, devamında (bölüm 3, 4 ve belki 5, artık Allah ne verdiyse!) diğer aralık olanakları için başka satırlar da ekleyeceğiz...:)

import std.algorithm;
import std.range;
import std.stdio;

auto r = iota(9);

alias R = typeof(r); // aralığın türü
alias E = int;       // elemanın türü

pragma(msg, isInputRange!(R, E));          // true
pragma(msg, isForwardRange!(R, E));        // true
pragma(msg, isBidirectionalRange!(R,  E)); // true
pragma(msg, isRandomAccessRange!(R, E));   // true

pragma(msg, isOutputRange!(R, E));         // false

void main()
{
  assert(r.equal([0, 1, 2, 3, 4, 5, 6, 7, 8]));
  auto yedek = r.save();
  r.popFront();
  assert(r.front == 1);
  assert(yedek.front == 0);

  // r aralığı üzerinde 1 adım zaten ilerlemiştik (-bknz. dropOne)
  auto r1 = r.chunks(2); // [[1, 2], [3, 4], [5, 6], [7, 8]]
  r1.map!"a.front + a.back".writeln; // [3, 7, 11, 15]

  // Bölüm 2a:
  struct SonsuzAralık(T)
  {
    T n;
    enum empty = false; // önce bu işletilir
    T front() => n; // ardından bu
    auto popFront() => n++; // ve bu
  }

  auto harfler = SonsuzAralık!char('a');
  harfler.take(26).writeln; // abcdefghijklmnopqrstuvwxyz

  alias TYPE = typeof(harfler);
  assert(isInfinite!TYPE);

  // Bölüm 2b:
  foreach(c; harfler)
  {
    c.write(" ");
    if(c == 'z') break;
  }
}

Derleme anında gördüğünüz gibi ilk dört olumlu pragma mesajı, aslında iota(9)'un kendi başına rasgele erişim de dahil (sanki bir dizi gibi) birçok aralık türünü desteklediğini kanıtlıyor. Ama bu testleri, kendi oluşturduğumuz türlerde yapsaydık muhtemelen eksik method'lar nedeniyle tam karşılanmadığını görecektiniz.

Yukarda önce bölüm 2a'da bir SonsuzAralık (kendi türümüzü) oluşturduk. Sonra bunun sahip olduğu şablon özelliği nedeniyle harflerden bir aralığımız oldu. Şablonlar için lütfen kitaptan ilgili bölümü okuyunuz: https://ddili.org/ders/d/sablonlar_ayrintili.html

Sonsuz olduğu için de take() olanağını kullanacağımızı söylemiştik. Böylece sınırları taşırmadan harfleri ekrana yazdırdık ve assert() ile sonsuz olduğunu kanıtladık. Ama istersek bölüm 2b'deki gibi her front() işletilirken ve foreach() içindeyken aralığın neresinde olduğumuzu kontrol edip ekrana yazma işlemini kendimiz sonlandırabilirdik. Yaptık da :)

Peki biraz daha kontrollü (tıpkı iota gibi) bir türümüz olsaydı nelere ihtiyacımız vardı:

auto _iota(T)(T n) => Aralık!T(0, n, 1);

struct Aralık(T)
{
  T n = 1, sınır, adım = 1;

  bool empty()
    => n >= sınır;

  T front()
    => n;

  auto popFront()
    => n += adım;
}

Fark ettiniz mi, aslında çok bir şey değiştirmedik, sadece 2 değişken üye daha ekledik. Hatta bunlar dilerseniz derleme zamanında belirlenen şablon parametreleri de olabilirdi. Sonsuz aralık için InputRange'in ilk üyesini şöyle de yapabilirsiniz:

  bool empty;/*()
    => n >= sınır;//*/

Çünkü bool'un varsayılan (init) değeri false'dı. Ama enum olmaması isInfinite!T şablonunun algılamasını engeller, bu da bonus olsun. Sonraki bölümlerde bu yeni _iota()'yı geliştireceğiz. Aslında dileyen 2 sene önce, Ali hocamın önderliğinde geliştirdiğiniz InclusiveRange v3'e göz atmak isteyebilir:
https://forum.dlang.org/post/ddwqgwvufkeieibpbwpw@forum.dlang.org

SDB@79

April 05

On Thursday, 4 April 2024 at 05:52:36 UTC, Salih Dincer wrote:

>

Sonraki bölümlerde bu yeni _iota()'yı geliştireceğiz.

Önce küçük bir düzeltme, Aralık kavramına bir özellik vermeliydik. Bu bir anlam, görev veya işlev olabilir. Dolayısıyla saymaya 0'dan başlayan (çünkü her türde 0'ın karşılığı vardır) bir aralığımız olsun, yani:

Aralık!E -> BaştanSay!E

alias E = double;
auto r = BaştanSay!E(5);

static this()
{
 alias ranges = imported!"std.meta".AliasSeq!(isRandomAccessRange,
 isForwardRange, isInputRange, isOutputRange, isBidirectionalRange);
 static foreach(R; ranges) pragma(msg, R!(typeof(r), E));
}
/* Ranges Test Truth Table:

       randomAccess   forWard   inPut   outPut   biDirectional
                                true
put                                      true
save                   true
opIndex    true
back, popBack                                        true   */

auto _iota(T)(T n) => BaştanSay!T(5); // Helper
struct BaştanSay(T)                   // Structure
{
  T sınır, adım = 1;
  private T n = 0;

  bool empty()
    => n >= sınır;

  T front()
    => n;

  auto popFront()
    => n += adım;

  auto length()
    => cast(size_t)((sınır - n) / adım);
  /*
  void put(T Sınır = 0, T Adım = 0)
  {
    sınır = Sınır > 0 ? Sınır : sınır;
    adım = Adım > 0 ? Adım : adım;
  }/*/

  auto save()
    => this;//*/
}

void main() {}

Dün en temelden başlamak kastıyla save()'e çok değinmedim. Aslında std.range.iota içerisinde bu method bulunduğundan belki araya sıkıştırabilirdik. Özellikle unuttuğum bu işlevi hemen yukarıda göreceksiniz...

Şimdi kodu çalıştırın ve telaşlanmayın çünkü main()'in boş olması bizi mutsuz etmeyecek. Hatta biraz eğleneceğiz de :) Eğer, dün bu kodu Aralık!int için çalıştırsaydınız sadece ortadaki, yani "InputRange is true" olduğunu görecektiniz:

>

false
false
true
false
false

Ama bugün save() dahil edildiği için en basit işlev dönüşüyle (return this'le) OutputRange de eklenmiş olacak. Tabi bu basit yapılar içindir! Yoksa daha karmaşık yapılarda derin çözümler kurgulamak gerekiyormuş. Kodu ilk haliyle çalıştırdıysanız, şimdi sadece put()'un üzerindeki comments satırına / karakterini ekleyin. Göreceksiniz ki yer değiştirecekler (flip/flop comments). Yani derleme esnasında şunu görmeniz gerek:

>

false
false
true
true
false

Eğer tüm comments'leri kaldırırsanız, 5'li Aralık'ımızdan baştaki RandomAccessRange ve BidirectionalRange haricinde, diğer 3'ü true olacaktır. Bunu static this() altına koyduğum yorumlarda ekstra bilgiyle beraber görebilirsiniz.

Sırada aralığımızı denemek kaldı. Lütfen yukarıdaki main()'i aşağıdaki ile değiştirin:

void main()
{
  auto r2 = r.save; // önce yedeğini alalım, çünkü ileride test edeceğiz!

  r2.writeln; // [0, 1, 2, 3, 4]
  r2.put(10); // veya r2.put(Sınır : 10);
  r2.writeln; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  r.put(Adım : 0.5);
  assert(r.length == r2.length);
}

Olayımız (1. hedefimiz) alıştığımız iota'nın desteklemediği put() kavramını esnek bir şekilde implement etmek. Diyelim ki eleman sayısı yukardaki gibi 5 olan aralığınızı iki katına çıkarmak istiyorsunuz. Matematiksel olarak 2 olasılığımız var: ya Sınır'la oynarız ya da Adım değişkeniyle. Bu tamamen size kalmış ama belli sınırlarda kalmak istiyorsanız (2. put'u) assert üzerindekini uygulamanız gerekecektir!

Çok uzattım, bitiriyorum... :)

Farkındaysanız hedefimiz olan elaman sayısını 2 katına çıkarmayı iki şekilde de D Compiler v2.108.0 sürümüyle uyumlu olarak çalıştırdık. Sanki bir esnek setter() işlevi gibi görülüyor. Bir sonraki entry'de ismini set() olarak değiştirip aralığı alan ve önceki aralığa ekleyen (ya da üzerine yazan, bilemiyorum henüz implement etmedim!) bir put() ile karşılaşabiliriz. Ayrıca 5 aralığın diğer ikisini (RandomAccessRange, BidirectionalRange) de true yapmalıyız! Bu 2. hedefimiz...

SDB@79