Jump to page: 1 2
Thread overview
February 19

Herkese selam...

Geçen ay yabancı forumda (-bknz. number ranges) iota'yı emekliye ayırabilecek harika bir aralık veren yapımız olmuştu; Ali hoca sayesinde...

Baktım da aradan tam 1 ay geçmiş ve tam unutmadan üzerinden geçelim derim. Haddim olmayarak 1-2 öneride bulunacağım çünkü orijinal iota(3) ile 0'dan 2'ye kadar aralık döndürebiliyormuş. Bizimkisi parametre serbestliği olmadığı için bunu yapamıyor.

Hadi, aşama aşama başlayalım:

/***********************************************/
/***                                         ***/
/***             Inclusive Range             ***/
/***                for D v2                 ***/
/***                                         ***/
/***      21 January 2022 by Ali Çehreli     ***/
/***                                         ***/
/***********************************************/
struct InclusiveRange(T) {
  private:
    T first, last;
    bool empty_;

  public T step; // SDB@79 ekledi

  this(U)(in U first,
          in U last,
          in U step) // SDB@79 ekledi
  in (first <= last, format!"\n
      Invalid range:[%s,%s]."(first, last))
  {
    this.first = first;
    this.last = last;
    this.step = step; // SDB@79 ekledi
    this.empty_ = false;
  }

Şimdi 2 konu kesin:

  • Ondalıklı sayılarda malum yuvarlama sorunları nedeniyle float, double, real türlerde kullanmayacağız/hata döndürebiliriz.

  • Ayrıca retro() imkanı varken yapıyı ekstradan olanaklar (BidirectionalRange) için genişletmeye gerek yok.

Aslında 3. parametre olan step de kesin çünkü Inclusive Range olarak anılabilmesi için bunun olmaması gerekiyor. Olursa bazı testlerden geçemiyor çünkü tam sınırlarda geziniyoruz. Yani sayıların üstünden atlayan atımız (pointer, gösterge) uçurum kenarında. Malum T.max'a ulaşınca yuvarlanıyoruz ama ben her şeye rağmen kullanıyorum o yüzden yukarda orijinal kodda olmayan kısımları SDB@79 ile imledim.

Peki şunlara (aşağıdaki kolaylık işlevidir) itirazım yok değil mi?

  • inclusiveRange(0, 1); => [ 0, 1 ]
    (Bunu iota'dan beklemiyoruz çünkü inclusive (kapsayan) özelliğini içermiyor.)

  • inclusiveRange(1, 1); => [ 1 ]

  • inclusiveRange(1, 0); => Exception!

İşte bu son durumu lezzetli bir şekilde, this() içinde ve sözleşmeli programlama marifetiyle (yukardaki kodlarda) hata attıyoruz. Bence gerek yok çünkü retro() kullanıyoruz ve tersten sıralanmış bir aralık istemiyoruz. O zaman önerim şu:

Exception atmasın diye o satırları kaldıralım ve yerine kolaylık işlevi içine yeni bir if() satırı ekleyelim:

if(last < first) return InclusiveRange!T(last, first, step);

Yetmez, hem parametre serbestliğini kullanalım hem de tür çıkarımı (inference) için de varsayılan bir tür seçelim, örneğin (T = int) olsun. Özetle şu örnekler böyle sonuçlanıyor:

  • inclusiveRange(1, 0); => [ 0, 1 ]

  • inclusiveRange(10); => [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

  • inclusiveRange(); => [ ]

Ancak istenmeyen bir durum var; o da yukardaki 3. örneğin boş dönse de length = 1 olması. İşte bunu nasıl çözerim bilmiyorum. Kod şöyle:

/* 19 Şubat Parametre Serbestliği Genişletildi */
auto inclusiveRange(T = int) (T f = cast(T)0,
                              T l = cast(T)0,
                              T s = cast(T)0) {
  static assert (!isBoolean!T, "\n
      Cannot be used with bool type\n");
  if (!s) s++;
  if (l < f) {
    return InclusiveRange!T (l, f, s);
  } else if (f == 0 && l == 0) {
    auto boşAralık = InclusiveRange!T (f, l, s);
         boşAralık.empty_ = true;

    return boşAralık;
  }
  return InclusiveRange!T (f, l, s);
}
February 19
On 2/19/22 11:43, Salih Dincer wrote:

> Exception atmasın diye o satırları kaldıralım ve yerine kolaylık işlevi
> içine yeni bir if() satırı ekleyelim:
>
> ```d
> if(last < first) return InclusiveRange!T(last, first, step);
> ```

O tür akıllılıklar yarardan çok zarar getirir çünkü kullanıcının olası hatasını gizlemiş oluyoruz. first <= last olacaksa olsun. Değilse hatadır diye düşünülmeli.

> Ancak istenmeyen bir durum var; o da yukardaki 3. örneğin boş dönse de
> length = 1 olması. İşte bunu nasıl çözerim bilmiyorum.

Ben onun çözümünü bir kaç kere söyledim ama galiba gözden kaçtı:

  return last - first + 1 - empty;

> auto inclusiveRange(T = int) (T f = cast(T)0,
>                                T l = cast(T)0,
>                                T s = cast(T)0) {

Şöyle de olur muydu?

  T f = T.init,
  // ...

>    static assert (!isBoolean!T, "\n
>        Cannot be used with bool type\n");
>    if (!s) s++;

Kullanıcının 0 vermesine izin mi veriyoruz yoksa step vermediğini o şekilde mi anlıyoruz? Ya gerçekten 0 verdiyse? Hatalı mı değil mi?

Eğer kullanıcının bildirmediğinde 1 yapacaktıysak da belki

  T s = T(1)

olurdu ve if'ten kurtulmuş olurduk.

Bu gibi durumlarda bir kaç tane kolaylık işlevi kullanılabilir:

auto inclusiveRange(T)(T first) {}
auto inclusiveRange(T)(T first, T second) {}
auto inclusiveRange(T)(T first, T second, T step) {}

>    if (l < f) {
>      return InclusiveRange!T (l, f, s);

Dediğim gibi, bence yanlış bir karar. :)

Ali

February 20
On Sunday, 20 February 2022 at 05:28:19 UTC, Ali Çehreli wrote:
> On 2/19/22 11:43, Salih Dincer wrote:
>
> >    static assert (!isBoolean!T, "\n
> >        Cannot be used with bool type\n");
> >    if (!s) s++;
>
> Kullanıcının 0 vermesine izin mi veriyoruz yoksa step vermediğini o şekilde mi anlıyoruz? Ya gerçekten 0 verdiyse? Hatalı mı değil mi?
>
> Eğer kullanıcının bildirmediğinde 1 yapacaktıysak da belki
>
>   T s = T(1)
>
> olurdu ve if'ten kurtulmuş olurduk.
>

Çok haklısınız hocam. Bu arada cast(T)0 yerine T(0) yapılabildiğini bilmiyordum.

Teşekkürler...
February 20
On 2/20/22 00:39, Salih Dincer wrote:

> cast(T)0 yerine T(0) yapılabildiğini
> bilmiyordum.

Bir çok sene geçmiştir ama göreceli olarak yeni bir olanak. C++'ta int(42) yazılabiliyordu, D'de de olsundu.

Ali

May 07

Merhaba,

Bu bayram biraz kodlamaya vakit bulabildim ve bir baktım, planladığım overloading (işleç yükleme) olayının büyük bir çoğunluğu bitmiş bile. Bir de Friedrich Gauss'un meşhur sum() özelliği ile hızlı toplar bir hale geldi ya, tadından yenmez hani...

Gerçi hala, parametresiz kullanımda işler karışıyor! Çünkü tek bir 0 rakamını içeren ve buna karşın boş olduğunu iddia eden bir aralık döndürmemesi lazım 😀

Neyse, başka hatalar için lütfen bu kodu test edin ve varsa hataları bildirin. Böylece kodu daha güvenilir bir hale getirmiş olabiliriz:

alias ir = inclusiveRange;
auto inclusiveRange(T = int)(T f = T(0), T l = T(0), T s = T(1)) {
  if(!l) {
    l = f;
    f = 0;
  }
  return InclusiveRange!T(f, l, s);
}

struct InclusiveRange(T) {
  T front, last, step, term;

  this(T front, T last, T step) {
    this.front = front;
    this.last = last;
    this.step = step;
    this.term = last;
    if(this.diff % step)
    {
      const t = cast(int)(this.diff / step);
      this.term = cast(T)(t * step + this.front);
    }
  }

  auto save() { return this; }
  auto opSlice() { return this; }
  auto opDollar() { return this.length; }
  auto opSlice(T a, T b) in (b<=this.length)
  {
    return InclusiveRange!T(
      cast(T)(front + a * step),
      cast(T)(front + (b - 1) * step), step);
  }

  bool opBinaryRight(string op:"in")(T lhs)
  {
    foreach(r; this)
    {
      if(isClose(lhs, r, 1e-6))
      {
        return true;
      }
    }
    return false;
  }

  T sum()
  {
    const residueCheck = front ? 2 * front + diff
                               : diff;
    return cast(T)(length * residueCheck / 2);
  }

  auto diff() { return term - front; }
  auto length() { return cast(int)(diff / step + 1); }
  bool empty() { return front > last; }
  T back() { return last; }
  void popBack() { if(!empty) last -= step; }
  void popFront() { if(!empty) front += step; }
}
May 07

On Saturday, 7 May 2022 at 18:43:07 UTC, Salih Dincer wrote:

>
    if(this.diff % step)
    {
      const t = cast(int)(this.diff / step);
      this.term = cast(T)(t * step + this.front);
    }

Burda kendimce bir chop olayını yapmaya çalıştım ama std.math.rounding.trunc saf işlevini kulanmak istediysem de olmadı. Neyse bu da iş görüyor...

>
  auto save() { return this; }
  auto opSlice() { return this; }
  auto opDollar() { return this.length; }
  auto opSlice(T a, T b) in (b<=this.length)
  {
    return InclusiveRange!T(
      cast(T)(front + a * step),
      cast(T)(front + (b - 1) * step), step);
  }

Birinci aşama yüklemeler şimdilik yeterli, sizce? Yukardaki save() olmayınca retro() çalışmıyor, dilimleme için diğerleri de lazım. Ama asıl olayı sondaki opSlice'da! Yeni bir aralık döndürmeyi yeğledim. Baştaki elemanı (front) referans alarak ve adımlama (step) değerini hesaplatarak eleman sayısından sınır değerine ulaştım..

>
  bool opBinaryRight(string op:"in")(T lhs)
  {
    foreach(r; this)
    {
      if(isClose(lhs, r, 1e-6))
      {
        return true;
      }
    }
    return false;
  }

Burası ise klasik ve aşağıdaki test kodunda D değişkenini aramasıyla çalıştığı kanıtlanıyor. Çok anlatmaya gerek yok sanırım? Evet, yavaş bir yöntem ama ek bir özellik işte.

>
  T sum()
  {
    const residueCheck = front ? 2 * front + diff
                               : diff;
    return cast(T)(length * residueCheck / 2);
  }

  auto diff() { return term - front; }
  auto length() { return cast(int)(diff / step + 1); }

İşte buradaki sum() özelliği çok işe yarayabilir. Çünkü sizi harici bir toplama işlevinden kurtarıyor ve hesaplayarak hızlıca toplamı veriyor. Aslında yukardaki 3 işlevde dağınık bir şekilde Gauss Toplamı var. Öyle ki length() de aradan çıkmış oldu. Bir taşla 2 kuş...

>
  bool empty() { return front > last; }
  T back() { return last; }
  void popBack() { if(!empty) last -= step; }
  void popFront() { if(!empty) front += step; }
}

Bura hakkında da çok bir şey yazmaya gerek yok: Çekirdek kısım! Gelelim teste...

Aşağıda ondalıklı aralıkları döndüren ve iota() ile fark&toplam karşılaştırması yapan bir test kodu var. Bunu denediğinizde, virgülden sonra 12 basamak hassasiyette ve real&double türler ile başarıyla geçtiğini görebilirsiniz.

import std.stdio;
import std.range;
import std.algorithm : Sum = sum;
//import std.math : isClose = approxEqual;/*
import std.math.operations: isClose;//*v2.091'den itibaren*/

void printRange(R)(R r, size_t s) {
  r[$-s..$].writefln!"[... %(%.12f, %)]: %s"(r.length);
}

void main() {
  alias testType = float; // real & double test-ok

  testType D = 0.991869918699;

  enum testParameters: testType
  {
    first, last, step, pow = 123
  }

  with(testParameters)
  {
    auto irTest = ir!testType(first, last, step/pow);
    auto ioTest = iota!testType(first, last, step/pow).array;

    testType irFark, ioFark;

    auto farkTest = irTest[$-2..$].array;
    irFark = farkTest[1] - farkTest[0];

    assert(isClose(farkTest[1], D, 1e-6));
    irTest.printRange(3);

    assert(D in irTest); // only with inclusiveRange

    farkTest = ioTest[$-2..$].array;
    ioFark = farkTest[1] - farkTest[0];

    assert(isClose(farkTest[1], D, 1e-6));
    ioTest.printRange(3);

    assert(irFark == ioFark);

    assert(isClose(irTest.sum, irTest.Sum, 1e-6));
    assert(isClose(irTest.sum, ioTest.Sum, 1e-12));
  }
}

Hatasız şu çıktıyı alıyorsanız, gerek dahili sum(), gerekse harici olanı (isim karışmasından emin olmak için import yaparken ismini değiştirdim) doğru topluyor demektir:

[... 0.959349593, 0.975609756, 0.991869919]: 62
[... 0.959349593, 0.975609756, 0.991869919]: 62

Eğer, testType = float yaparsanız, o zaman 1e-12'leri 6'ya indirmekte fayda var. Malum teknik nedenler!

Başarılar...

May 08
On 5/7/22 12:26, Salih Dincer wrote:

> İşte buradaki `sum()` özelliği çok işe yarayabilir. Çünkü sizi harici
> bir toplama işlevinden kurtarıyor ve hesaplayarak hızlıca toplamı
> veriyor.

Düşünce güzel ama yine de bu türün asıl işiyle pek ilgisi olmayan bir işlem değil mi? İnsan acaba başka hangi işlemler daha etkin yaptırılabilir diye düşünüyor. Programcılıkta hep aradığımız basitlikten ve bağımsızlıktan çok mu uzaklaşmış oluruz?

sum, elemanların toplamı ise (öyle mi?) step de formülün bir parçası olmamalı mı?

> bir test kodu var.

Bu açıklamalar yerine unittest de olabilir miydi? ;)

Ben basitten başladım ve aşağıdaki assert'lerin sonuncusunu geçiremedim. (inclusiveRange(0)'ın iota(0) gibi işleyeceğini varsaydım.)

unittest {
  import std.range;
  import std.algorithm;

  // iota'nın bir fazlalı kullanımına eşit olmalı
  assert(inclusiveRange(0, 10).equal(iota(0, 11)));

  // Başka aralık algoritmalarıyla (örneğin, take) kullanılabilmeli
  assert(inclusiveRange(0, 20).take(11).equal(iota(0, 11)));

  // Tek parametre değeri çok uzun bir aralık olmalı (mı?)
  assert(inclusiveRange(0).take(11).equal(iota(0).take(11)));
}

Ali

May 08

On Sunday, 8 May 2022 at 15:24:37 UTC, Ali Çehreli wrote:

>

On 5/7/22 12:26, Salih Dincer wrote:

>

İşte buradaki sum() özelliği çok işe yarayabilir. Çünkü
sizi harici
bir toplama işlevinden kurtarıyor ve hesaplayarak hızlıca
toplamı
veriyor.

Düşünce güzel ama yine de bu türün asıl işiyle pek ilgisi olmayan bir işlem değil mi? İnsan acaba başka hangi işlemler daha etkin yaptırılabilir diye düşünüyor. Programcılıkta hep aradığımız basitlikten ve bağımsızlıktan çok mu uzaklaşmış oluruz?

sum, elemanların toplamı ise (öyle mi?) step de formülün bir parçası olmamalı mı?

>

bir test kodu var.

Bu açıklamalar yerine unittest de olabilir miydi? ;)

Ben basitten başladım ve aşağıdaki assert'lerin sonuncusunu geçiremedim. (inclusiveRange(0)'ın iota(0) gibi işleyeceğini varsaydım.)

unittest {
import std.range;
import std.algorithm;

// iota'nın bir fazlalı kullanımına eşit olmalı
assert(inclusiveRange(0, 10).equal(iota(0, 11)));

// Başka aralık algoritmalarıyla (örneğin, take) kullanılabilmeli
assert(inclusiveRange(0, 20).take(11).equal(iota(0, 11)));

// Tek parametre değeri çok uzun bir aralık olmalı (mı?)
assert(inclusiveRange(0).take(11).equal(iota(0).take(11)));
}

Ali

void main()
{
  auto irTest = ir(10);
  auto ioTest = iota(11);
  auto tkTest = inclusiveRange(0, 10).take(11);

  irTest.sum.writeln; // 55
  // ucuz ve yükü olmayan bir olanak

  assert(irTest.equal(ioTest)); // hata yok
  assert(tkTest.equal(ioTest)); // hata yok

  inclusiveRange(0).take(11).writeln; // [0]
  iota(0, 1).take(11).writeln; // [0]
}

Zaten 0 dönmemesi gerektiğini daha önce belirtmiştik. En az 100 satır unittest yazmıştım ama take() ile denememiştim. Sadece kalabalık olmasın diye paylaşmıyorum.

Teşekkür ederim...

May 08
On 5/8/22 11:41, Salih Dincer wrote:

>    inclusiveRange(0).take(11).writeln; // [0]
>    iota(0, 1).take(11).writeln; // [0]

Kafam karışmış! :) Aslında aşağıdakini denediğimi sanmıştım.

  assert(inclusiveRange(10).equal(iota(11)));

O doğru çalışıyor.

Bir tane daha düşündüm. (Bunu da konuşmuştuk galiba.) Aşağıdaki aralık hiç sonlanmıyor:

  inclusiveRange(ubyte.min, ubyte.max)

Sonlanmasını bekliyor muyuz?

Ali

May 09

On Sunday, 8 May 2022 at 20:18:31 UTC, Ali Çehreli wrote:

>

On 5/8/22 11:41, Salih Dincer wrote:

>
   inclusiveRange(0).take(11).writeln; // [0]
   iota(0, 1).take(11).writeln; // [0]

Kafam karışmış! :) Aslında aşağıdakini denediğimi sanmıştım.

assert(inclusiveRange(10).equal(iota(11)));

Estağfirullah hocam, asıl kafa karışıklığı bende :)

Öncelikle take() ile hiç deneme yapmadığımdan hemencecik yolda yürürken denemiştim! Siz daha geniş bir aralığı bana gösterdiniz, şöyle olmalıydı:

auto tkTest = inclusiveRange(0, 20).take(11);

Gerçi değişen bir şey yok ya neyse bunu geçtik. Aslında iota(0) konusunda da haklısınız çünkü çıktısı [] şeklindedir.

On Sunday, 8 May 2022 at 20:18:31 UTC, Ali Çehreli wrote:

>

On 5/8/22 11:41, Salih Dincer wrote:

Bir tane daha düşündüm. (Bunu da konuşmuştuk galiba.) Aşağıdaki aralık hiç sonlanmıyor:

inclusiveRange(ubyte.min, ubyte.max)

Sonlanmasını bekliyor muyuz?

Bu da bilinçli değinmedim sorun. Malum türlerin sınırı olduğu için ve Inclusive Range hepsini kapsamaya çalıştığından patlıyor.

Özetle, bu kadarı kadı kızında da olur diyorum ve bu iki durum ile yaşayabilirim. :)

Arkadaşlar, bu ikisi dışında başka varsa ya konuşun ya da ilelebet susun 😀

Sevgiler, saygılar...

« First   ‹ Prev
1 2