Thread overview
Appender (İliştirici) Sarması
Sep 28, 2022
Salih Dincer
Sep 28, 2022
Ali Çehreli
Sep 28, 2022
Salih Dincer
Sep 28, 2022
Ali Çehreli
Sep 28, 2022
Salih Dincer
Sep 28, 2022
Ali Çehreli
Sep 29, 2022
Salih Dincer
Sep 29, 2022
Salih Dincer
Sep 29, 2022
Ali Çehreli
Sep 29, 2022
Salih Dincer
September 28, 2022

Merhaba,

Şimdi şu kısacık koda bakın, bunu Steven Schveighoffer yazmıştı ve şu şekilde kırpıp uyguladım:

struct İliştirici(T) {
  T[] *arr;
  void put(R)(R value) { (*arr) ~= value; }
}

auto iliştirici(T)(return ref T[] arr) {
   return İliştirici!T(&arr);
}

Elbete bu konu, D'de bir diziye ~= işleçleriyle ekleme/iliştirme/katma ile ilgili (yabancı forumda put'u diğer dillerin etkisiyle karıştırılabileceğinden bahsediyor) ve yapacağımız tek satırda bu kadar da basit bir şey. Ancak işin içine işlevler, yapılar arasında veri paylaşımı ve parametre kopyalama gibi şeyler girince veri kaynağını iyi yönetemediğinizi fark ediyorsunuz. Çoğu zaman std.array'deki Appender'ı kullanırız. Şimdiki karşılaştırma onu geride bırakıp kendi İliştirici'mizi değerli kılıyor:

import std.stdio;
void main()
{
  string[] bir = ["bir"];
  bir.iliştirici.put = ["2", "3"];
  bir.writefln!"%(%s\n%)";

  writeln;
  import std.array;

  string[] iki = ["iki"];
  appender(&iki).put = ["3", "4"];
  iki.writefln!"%(%s\n%)";
} /* ÇIKTISI:
"bir"
"2"
"3"

"iki"
"3"
"4
*/

Düşünceleriniz neler, sizce de çok lezzetli değil mi?

Başarılar...

September 27, 2022
On 9/27/22 19:05, Salih Dincer wrote:

> (yabancı forumda put'u diğer dillerin etkisiyle
> karıştırılabileceğinden bahsediyor)

O konunun başındaki (ve Steve'in kodunun çözdüğü) sorun şu:

import std;

void main() {
    auto dizi = [1, 2];
    dizi.put(3);
    writeln(dizi);    // [2] yazar
}

3 nereye gitti? Bu davranışın garip ve tutarsız olduğu vurgulanıyor. Evet, gariptir ama başkalarının da gösterdiği gibi tutarlıdır.

put'un bu garip özelliğine karşı bir çözüm, ona tüketeceği yeni bir dilim vermektir:

import std;

void main() {
    auto dizi = [1, 2];
    auto dizi2 = dizi;    // Yeni dilim
    dizi2.put(3);         // Yeni dilim tükenir
    writeln(dizi);        // [3, 2] yazar; şimdi oldu
}

> Appender'ı kullanırız. Şimdiki karşılaştırma onu geride bırakıp kendi
> İliştirici'mizi değerli kılıyor:

Appender belleği dilimlerden daha iyi kullandığından genelde daha hızlı işler. Örneğin şuralarda belleği bütünüyle çöp toplayıcıya bırakmadığı ve kendi başının çaresine baktığı görülüyor.

  https://github.com/dlang/phobos/blob/master/std/array.d#L3559

GC genel amaçlı olduğundan, benim cached'i yazarken uyguladığım "baştan eleman kırp, sona eleman ekle" yönündeki kullanımımda her eleman için bellek ayrıldığını gözlemlemiştim.

Appender onu yapmazdı ama onun da "baştan eleman kırp" yeteneği yoktu. Ben de o yüzden CircularBlocks ve ReusableBlock'ı yazmıştım:

  https://code.dlang.org/packages/alid

Pişman değilim! :p

Ali


September 28, 2022

On Wednesday, 28 September 2022 at 05:23:15 UTC, Ali Çehreli wrote:

>

On 9/27/22 19:05, Salih Dincer wrote:
3 nereye gitti? Bu davranışın garip ve tutarsız olduğu vurgulanıyor. Evet, gariptir ama başkalarının da gösterdiği gibi tutarlıdır.

put'un bu garip özelliğine karşı bir çözüm, ona tüketeceği yeni bir dilim vermektir:

Daha ilgincini, yukardaki kolaylığa ek olarak std.range'dekiyle bir deneme yaptığımızda görüyoruz:

  string[] bir = ["bir"];
  bir.iliştirici.put = ["2", "3"];
  assert(bir == ["bir", "2", "3"]);

  import std.range;

  auto slice = bir[];
  slice.put("4");
  bir.writeln; // ["4", "2", "3"]

Çıkan bu sonuç için soralım peki "bir" nereye gitti? Tabii ki "4" üzerine yazıldı. Ben şahsen, dizi sonuna eklemesini beklerim!

Teşekkürler....

September 28, 2022
On 9/28/22 03:39, Salih Dincer wrote:

> Daha ilgincini, yukardaki kolaylığa ek olarak `std.range`'dekiyle bir
> deneme yaptığımızda görüyoruz:
>
> ```d
>    string[] bir = ["bir"];
>    bir.iliştirici.put = ["2", "3"];
>    assert(bir == ["bir", "2", "3"]);
>
>    import std.range;
>
>    auto slice = bir[];
>    slice.put("4");
>    bir.writeln; // ["4", "2", "3"]
> ```
>
> Çıkan bu sonuç için soralım peki "bir" nereye gitti?

Dilimlerin OutputRange olarak kullanılmasını görüyoruz. Öyle bir çıkışın sonuna gelindiğinin copy() gibi bir algoritmaya bildirilebilmesi için düşünülen yöntem, dilimin tükenmesi olmuş. Yani, "şu dilimin üzerine yaz; bitince yer kalmadı demektir" diye düşünülmüş.

> Tabii ki "4"
> üzerine yazıldı. Ben şahsen, dizi sonuna eklemesini beklerim!

O zaten dil olanağı olarak ~= işleciyle var. Onda sorun, sonuna gelindiğini bildiremiyoruz. Örneğin, bir algoritma kaynak tükenene kadar ekleyebilir ama çıkışın tükendiği bilgisini elde edemez.

Ali


September 28, 2022

On Wednesday, 28 September 2022 at 14:24:22 UTC, Ali Çehreli wrote:

>

On 9/28/22 03:39, Salih Dincer wrote:

>

Daha ilgincini, yukardaki kolaylığa ek olarak
std.range'dekiyle bir
deneme yaptığımızda görüyoruz:

   string[] bir = ["bir"];
   bir.iliştirici.put = ["2", "3"];
   assert(bir == ["bir", "2", "3"]);

   import std.range;

   auto slice = bir[];
   slice.put("4");
   bir.writeln; // ["4", "2", "3"]

Çıkan bu sonuç için soralım peki "bir" nereye gitti?

Dilimlerin OutputRange olarak kullanılmasını görüyoruz. Öyle bir çıkışın sonuna gelindiğinin copy() gibi bir algoritmaya bildirilebilmesi için düşünülen yöntem, dilimin tükenmesi olmuş. Yani, "şu dilimin üzerine yaz; bitince yer kalmadı demektir" diye düşünülmüş.

Şimdi daha iyi anlıyorum! Teşekkürler hocam, ufkumu açtınız. Gerçi bu örneğe takılıp kalmak istemiyorum ama malum soru yine karşıma çıktı: 😀

"1" nereye gitti?


  import std.range;
  auto slice = bir[];
  slice.put("4");
  bir.writeln; // ["4", "2", "3"]

  slice.put("3");
  bir.writeln; // ["4", "3", "3"]

  slice.put("2");
  bir.writeln; // ["4", "3", "2"]

  slice.length++; /* bu olmazsa şu hatayı alırız:
    Attempting to fetch the front of
    an empty array of string //*/
  slice.put("1");
  bir.writeln; // ["4", "3", "2"]

  bir.length++;
  bir.writeln; // ["4", "3", "2", ""]

Sevgiler, saygılar...

September 28, 2022
On 9/28/22 10:30, Salih Dincer wrote:

>    auto slice = bir[];
>    slice.put("4");
>    slice.put("3");
>    slice.put("2");
>    bir.writeln; // ["4", "3", "2"]

Tamam, var olan elemanların üzerlerine yazdık.

>    slice.length++; /* bu olmazsa şu hatayı alırız:
>      Attempting to fetch the front of
>      an empty array of string //*/

Uzunluğu arttırınca 'slice' yepyeni bir yeri göstermeye başlar ya... Artık 'bir' ile ilgisi kalmadı. (.ptr nitelikleri bunu gösterecektir.)

>    slice.put("1");

slice'ın tek elemanının üzerine yazıldı ama elimizde oraya erişen bir referans kalmadığından göremeyiz.

>    bir.writeln; // ["4", "3", "2"]
>
>    bir.length++;
>    bir.writeln; // ["4", "3", "2", ""]

Evet, dinamik dizilerin uzunluğu arttırılabilir. ;)

Ali


September 29, 2022

On Wednesday, 28 September 2022 at 19:27:47 UTC, Ali Çehreli wrote:

>

On 9/28/22 10:30, Salih Dincer wrote:

>

auto slice = bir[];
slice.put("4");
slice.put("3");
slice.put("2");
bir.writeln; // ["4", "3", "2"]

Tamam, var olan elemanların üzerlerine yazdık.

Evet hocam, tıpkı Appender'ın put'unda olduğu gibi bir aralık (birden fazla elemanı bulunan bir dizi) de eklenebiliyormuş. Yani slice.put(["4", "3", "2"]) oluyormuş.

Son bir soru: Aşağıda Appender.put'un aralık ekleyen özelliğinin açıklaması var. Sayfasında char'lar için encoding farklıysa kodlama da yapacağından bahsediyor ama örnek vermemiş.

>

Appends an entire range to the managed array. Performs encoding for char elements if A is a differently typed char array.

Burda tam olarak ne kast ediliyor? Sanki std.range'deki put'a göre bir avantaj söz konusu (olabilir) diyeceğim ama emin değilim!

Son bir tespit: Ve tabi aşağıdaki belki de gereksiz bir deneme! Çünkü Appender'ın put'una göre (EKLE'nin) hiçbir farkı yok (gibi!):

import std.stdio;
void main()
{
  import std.range : EKLE = put;
  string[] bir = ["bir"];
  auto app = appender(&bir);

  app.EKLE(["2", "3", "4"]);
  bir.writeln;

  app.EKLE(["5"]);
  bir.writeln;
}

Başarılar...

September 29, 2022

On Thursday, 29 September 2022 at 04:41:50 UTC, Salih Dincer wrote:

>

Appender'ın put'una göre (EKLE'nin) hiçbir farkı yok [...]

Belki de böyle olmasının sebebi std.array.Appender'ın bize bir portal/arayüz (sınıflardaki interface'i kastetmiyorum!) sunması. Yani aslında, son örnekte sanki Appender'ın put'unu kullanıyoruz diyeceğim ama bu arayüzün InputRange'leri yok yani EKLE(std.range.put) burada kendi başına çalışıyor!

Peki reserve ile kapasite arttırımına gitmediğimiz halde niye önceki denemelerde olduğu gibi hata almadık? Sanki ~= işleçleri ile normal bir şekilde diziye ekleme yapar gibiyiz!

Eee tabi Ali hoca, bu arayüz gibi tanımladığım yapının aslında GC'nin ötesine geçip kendi bellek tahsisini yaptığından bahsetmişti. Dolayısıyla bizi daha hızlı bir erişim imkanı sunuyor.

Sevgiler, saygılar...

September 29, 2022
On 9/28/22 21:41, Salih Dincer wrote:

>> Performs encoding for
>> char elements if A is a differently typed char array.

Her üç karakter türünü (char, wchar, ve dchar) kabul edeceğini ve gerektikçe  dönüşüm yapacağını kastediyor.

>    app.EKLE(["2", "3", "4"]);

Büyük olasılıkla, put'un yüklemelerinden birisi, Appender'ın bir OutputRange olduğunu görüyor ve onun .put üye işlevini çağırıyor.

Ali


September 29, 2022

On Thursday, 29 September 2022 at 11:43:25 UTC, Ali Çehreli wrote:

>

Büyük olasılıkla, put'un yüklemelerinden birisi, Appender'ın bir OutputRange olduğunu görüyor ve onun .put üye işlevini çağırıyor.

Evet, yaklaşık 500 - 600 satırlık Appender çok güzel yazılmış; satır satır inceledim! Özellikle şu satırda std.range.primitives.d'deki InputRange işlevleri ile bağlantı yakaladım:

Source: https://github.com/dlang/phobos/blob/0448900fc5a05f2b076eb2500e2522b43c2227cc/std/array.d#L3604

private template canPutRange(Range)
    {
        enum bool canPutRange =
            isInputRange!Range &&
            is(typeof(Appender.init.put(Range.init.front)));
    }

Teşekkürler...