Thread overview
SONY-1: Short Scale Numbers
Jan 22, 2022
Salih Dincer
Jan 22, 2022
Ali Çehreli
Jan 23, 2022
Ali Çehreli
Jan 23, 2022
Salih Dincer
Jan 24, 2022
Ali Çehreli
Jan 24, 2022
Ali Çehreli
Jan 29, 2022
Salih Dincer
Jan 29, 2022
Ali Çehreli
Feb 13, 2022
Salih Dincer
January 22, 2022

Merhaba,

Bu hafta yeni bir seriye başladık:
(BU BİR REKLAM DEĞİLDİR!)

Sadece tesadüf, SONY: Siz Olsanız Nasıl Yapardınız?

Amaç yarışmak değil, sadece gökkuşağı renkleri kadar çeşitli olan insan zekasını açığa çıkarmak. O yüzden kopya çekmek yok. Cevaplara sadece sonucu çıkarabildiğinizde bakacaksınız:

Meydan Okuyorum: Bir long sayısını, aralarında "bin, milyon, milyar" yazacak şekilde benden daha az satır kodla string'e çeviremezsiniz!

// GİRDİ
long[] girdiler = [ 741, 1_500, 5_005, 1_250_000, 3_000_042, 10_000_000, 999_001_001_001 ];

// SKALA
enum s { Bir, Bin=10^^3, Milyon=10^^6, Milyar=10^^9 }

Not: İngilizce ile uyumlu olması için (a/an), skala başında 1 rakamı varsa yazıyıla "bir" yazsın.

// ÇIKTI
İlk girdi için, sadece "741" yazarken, 5. girdi "3 Milyon 42" yazacak. Bir de Türkçe'ye özel, bin skalası için özel durum var:

  • Bin 500 (başında "Bir" yazmayacak)
  • 5 Bin 5 (başında sayı olacak)
  • Bir Milyon 250 Bin
  • 10 Milyar
  • 999 Milyar Bir Milyon Bin 1

Kolay gelsin...

January 22, 2022
On 1/22/22 00:19, Salih Dincer wrote:
> Bir long sayısını, aralarında "bin, milyon, milyar"
> yazacak şekilde benden daha az satır kodla string'e çeviremezsiniz!

Ben uzunluğu önemsemedim (henüz?) ve biraz daha becerikli oldu:

- Kentilyon'a kadar destekliyor. (Daha fazlası ulong'a sığmıyor.)

- "sıfır" ve "eksi" de yazıyor.

- Aynı Appender'ın belleğini kullandığı için fazla bellek de ayırmaz.

struct Bölen {
  ulong değer;
  string söz;
}

static const(Bölen[]) bölenler;

static this() {
  import std.range : array, enumerate;
  import std.algorithm : map;

  bölenler = [ "", "bin", "milyon", "milyar",
               "trilyon", "katrilyon", "kentilyon", ]
             .enumerate
             .map!(t => Bölen(10^^(t.index * 3), t.value))
             .array;
}

import std.format : format;

void sözleImpl(T, Ç)(T sayı, ref Ç çıktı)
in (sayı > 0, format!"Geçersiz sayı: %s"(sayı)) {
  import std.range : retro;
  import std.format : formattedWrite;

  foreach (bölen; bölenler.retro) {
    const bölüm = sayı / bölen.değer;

    if (bölüm) {
      if ((bölen.değer != 1000) || (bölüm != 1)) {
        çıktı.formattedWrite!" %s"(bölüm);
      }

      çıktı.formattedWrite!" %s"(bölen.söz);
    }

    sayı %= bölen.değer;
  }
}

// Her çağrı bir önce döndürülen string'in üzerine
// yazar. Gerektiğinde dönüş değerini kopyalayın.
auto sözle(T)(T sayı) {
  import std.array : Appender;
  import std.string : strip;

  static Appender!(char[]) çıktı;
  çıktı.clear;

  if (sayı == 0) {
    çıktı ~= "sıfır";

  } else {
    if (sayı < 0) {
      çıktı ~= "eksi";
      sayı = -sayı;
    }

    sözleImpl(sayı, çıktı);
  }

  return çıktı.data.strip;
}

void main() {
  import std.stdio;
  import std.algorithm;

  long[] girdiler = [ 741, 1_500, 5_005, 1_250_000, -3_000_042, 10_000_000, 999_001_001_001, 0 ];
  writefln!"%-(%s\n%)"(girdiler.map!sözle);
}

Ali

January 23, 2022
Benim çözümde sorunlar var.

On 1/22/22 15:37, Ali Çehreli wrote:

> struct Bölen {
>    ulong değer;

Değer ulong seçilmiş olduğundan bu kod int ile çalışamaz çünkü int aşağıdaki bazı işlemler sırasında otomatik olarak ulong'a dönüştürülür.

>    bölenler = [ "", "bin", "milyon", "milyar",
>                 "trilyon", "katrilyon", "kentilyon", ]

short ile çalışırken "bin"den ötesi gerekmez ve hatta 'bölenler'in değerleri mecburen ulong olduğundan işlemler yanlış olur.

>        sayı = -sayı;

O işlem, int'ten küçük türlerle artık çalışmıyor(muş).

O hatalar nedeniyle program örneğin sıfırdan küçük bir int değerle çalışmıyordu.

Bu sorunları çözdüğümü sanıyorum ama henüz paylaşmıyorum. :) Buna rağmen, şimdiki çözüm de yukarıdaki '-sayı' işlemi nedeniyle işaretli türlerin .min değerleriyle çalışamıyor. Bunun nedeni, örneğin sayı int.min olduğunda -sayı'nın değeri yine int.min'dir! Bakınız:

  static assert(-int.min == int.min);  // Delilik! :)

Neyse ki, elimdeki çözüm hiç olmazsa bu durumun geçersiz olduğunu bildiriyor.

Ali

January 23, 2022

On Sunday, 23 January 2022 at 08:51:20 UTC, Ali Çehreli wrote:

>

Benim çözümde sorunlar var.

On 1/22/22 15:37, Ali Çehreli wrote:

>

struct Bölen {
ulong değer;

Değer ulong seçilmiş olduğundan bu kod int ile çalışamaz çünkü int aşağıdaki bazı işlemler sırasında otomatik olarak ulong'a dönüştürülür.

Bende de sayılar büyükdükçe L harfi ile betimlemem gerekti. Teorik olarak sorunsuz katrilyona çıktım ve aynı kodlar üzerinden İngilizce versiyonunu yaptım. Henüz sizin kodlara bakamadım çünkü dışardayım; karantinam bitti de :)

Önce 2 işlevlik bir struct yaptım ama sonra beğenmedim dışarı çıkartıp 2. basamakla ile beraber karşılaştırdım. İlla ki aralarında performans farkı vardır. Satır sayısı ise test hariç 2 x 15 diyebiliriz?

long[] girdiler = [ 741, 1_500, 2_001,
  5_005, 1_250_000, 3_000_042, 10_000_000,
  1_000_000, 2_000_000, 100_000, 200_000,
  10_000, 20_000, 1_000, 2_000, 74, 7,
  1_001_001_001, 999_001_001_001 ];

import std.conv, std.stdio;

void main() {
  long[6] b, b2; // v1, v2 için basamaklar
  enum s {
    Bir = 1L, Bin = 10^^3L,
    Milyon = 10^^6L, Milyar = 10^^9L,
    Trilyon = 10^^12L, Katrilyon = 10^^15L
  }

  void basamakla1(long i) {
    b[0] = i % s.Bin;
    b[1] = i - b[0];

    b[2] = b[1] - (b[1] % s.Milyon);
    b[1] = (b[1] % s.Milyon) / s.Bin;

    b[3] = b[2] - (b[2] % s.Milyar);
    b[2] = (b[2] % s.Milyar) / s.Milyon;

    b[4] = b[3] - (b[3] % s.Trilyon);
    b[3] = (b[3] % s.Trilyon) / s.Milyar;

    b[4] /= 10^^12L;
  } // 14-15 satır

// ismi, eskiden toString()'di...
  string güzelSayı() {
    string r;
    r ~= b[3] > 1 ? " " ~ to!string(b[3]):
         b[3] ? " " ~ to!string(s.Bir)   : "";
    r ~= b[3] ? " " ~ to!string(s.Milyar): "";

    r ~= b[2] > 1 ? " " ~ to!string(b[2]):
         b[2] ? " " ~ to!string(s.Bir)   : "";
    r ~= b[2] ? " " ~ to!string(s.Milyon): "";

    r ~= b[1]  > 1 ? " " ~ to!string(b[1]): "";
    r ~= b[1]  ? " " ~ to!string(s.Bin)   : "";
    r ~= b[0]  ? " " ~ to!string(b[0])    : "";
    return r;
  } // 14-15 satır

  void basamakla2(long i) {
    import std.array, std.range, std.algorithm, std.format;

    auto parçala = format("%,?3d", '|', i).split('|');
    auto sayılar = parçala.retro.array;
    foreach(basamak, sayı; sayılar) {
//    v--- burayı unutma
      b2[basamak] = to!long(sayı);
    }
  } // 6-7 satır

  // ###      TESTLER:      ####
  auto tekTest = girdiler[$-2];//1001001001
  basamakla1(tekTest);
  basamakla2(tekTest);

  // iddia ediyorum, her 2 basamaklama yöntemi eş
  assert(b2 == b);

  // madem eş, her ikisi de aynı sonucu verecek:
  b2.writeln(" =? eşitmiş!\n", b);
  güzelSayı.writefln!"|%s|\n";

  foreach(i, g; girdiler) {
    writef("%,?3d: ",'.', g);
    i.writeln(". test");
    basamakla1(g);/* Dikkat: tekli testten dolayı,
                   * yöntemler arasında geçiş
                   * yapılacaksa değişiklik şart'
                   */
    güzelSayı.writefln!"|%s|\n";
  }
} /* ÇIKTISI: Kesinlikle istediğim gibi ama
 sadece en baştaki boş karakteri halledemedim!*/

Başarılar...

January 23, 2022
On 1/23/22 06:21, Salih Dincer wrote:

> Önce 2 işlevlik bir struct yaptım ama sonra beğenmedim

Sohbetlerde de bir kaç kere değindiğimiz gibi, ben artık hep işlevle başlıyorum; bir neden ortaya çıktıkça struct'a yöneliyorum. class çok az kullanılıyor.

>   sadece en baştaki boş karakteri halledemedim!*/

Ben sonucu string olarak üretip basitçe std.string.strip'i çağırdım. Aşağıdaki sözde koddaki gibi çok hızlı bir işlevdir:

  char[] dizgi = /* ... */;
  while (boşluk_mu(dizgi.front)) {
    dizgi = dizgi.popFront();
  }
  // Aynısı arka taraf için...

Benim son programım aşağıda. T.min ile ilgili sorunum var çünkü yalnızca pozitif sayılarla işleyen işlevi -sayı değeriyle çağırıyorum. İlginç olarak, '-sayı'nın türü otomatik olarak 'int' olabiliyor (sayı örneğin 'short' olsa bile). O yüzden bazı karmaşıklıklar da içeriyor.

Herşeyi açıklamalarda ve birkaç unittest ile anlatmaya çalıştım:

import std.traits : isSigned;

// Belirtilen sayıyı sözle ifade eder. Her çağrı bir önce döndürülen
// string'in üzerine yazar. Gerektiğinde dönüş değerini kopyalayın.
// T.min değeri ile çağrılamaz.
auto sözle(T)(T sayıParametre) {
  import std.array : Appender;
  import std.string : strip;
  import std.traits : Unqual;
  import std.exception : enforce;

  // Bu algoritma işaretli sayıların T.min değeriyle çalışamaz çünkü
  // basitçe '-T.min' yaparak onun değerini yazdırmaya çalışır. Ancak,
  // işaretli türlerde '-T.min == T.min' ilişkisi geçerli olduğundan o
  // amaca ulaşamayız. O yüzden bu değeri burada denetliyoruz.
  static if (isSigned!T) {
    enforce(sayıParametre != T.min, format!"Geçersiz sayı: %s"(sayıParametre));
  }

  // Giriş 'const' bile olsa onun const olmayan bir kopyasını
  // kullanacağız. (Unqual, 'const' gibi "qualifier"ları kaldırır.)
  auto sayı = cast(Unqual!T)sayıParametre;

  // Her sonuç için farklı bellek ayrılmasın diye hep bu ortak belleği
  // kullanacağız.
  static Appender!(char[]) çıktı;
  çıktı.clear;    // Belleği başa sarıyoruz.

  if (sayı == 0) {
    çıktı ~= "sıfır";

  } else {
    if (sayı < 0) {
      çıktı ~= "eksi";
      static if (T.sizeof < int.sizeof) {
        // Burada kısaca
        //
        //   sayı = -sayı
        //
        // yazmak isterdim ama ilginç bir otomatik dönüşüm konusu var:
        // 'sayı' int'ten küçük bir tür olduğunda, örneğin 'short'
        // olduğunda '-sayı'nın türü int midir, short mudur? Yeni
        // derleyiciler -preview=intpromote ile ilgili bir uyarı
        // veriyorlar. O yüzden kendim açıkça önce int'e sonra T'ye
        // dönüştürüyorum.
        sayı = cast(T)(-cast(int)sayı);

      } else {
        // int'ten büyük türlerde ise hiç de cast(int) diye değer
        // kaybetmek istemeyiz. Basit işlem doğru çalışır:
        sayı = -sayı;
      }
    }

    // Bu ön işlemlerden sonra asıl işlevi çağırabiliriz
    sözleImpl(sayı, çıktı);
  }

  // Baştaki ve sondaki boşlukları çıkartmayı unutmadan
  return çıktı.data.strip;
}

unittest {
  import std.exception : assertThrown;

  assertThrown(sözle(int.min));

  assert(1_001_500.sözle == "1 milyon bin 500");
  assert(1_002_500.sözle == "1 milyon 2 bin 500");
}

import std.format : format;
import std.range : isOutputRange;

// Sözleri üreten asıl işlev bu. Sonucu 'çıktı' akımına
// yazar. Kullanıcılar normalde bunu değil, sözle() işlevini
// çağırırlar. Eksi sayılar sözle() tarafından halledilirler.
void sözleImpl(T, Ç)(T sayı, ref Ç çıktı)
if (isOutputRange!(Ç, char))
in (sayı > 0, format!"Geçersiz sayı: %s"(sayı)) {
  import std.range : retro;
  import std.format : formattedWrite;

  foreach (bölen; bölenler!T.retro) {
    const bölüm = sayı / bölen.değer;

    if (bölüm) {
      // "1 bin" durumunda 1 yazmayalım
      if ((bölen.değer != 1000) || (bölüm != 1)) {
        çıktı.formattedWrite!" %s"(bölüm);
      }

      çıktı.formattedWrite!" %s"(bölen.söz);
    }

    sayı %= bölen.değer;
  }
}

// İşlemlerin otomatik tür dönüşümü nedeniyle istenmeyen türlere
// kaymasını önlemek için her tür sayı için farklı türde Bölen
// gerçekleştirmeleri olsun diye bunu şablon yaptım. Kodun geri
// kalanında şablon kullanmamın nedeni de oydu.
struct Bölen(T) {
  T değer;       // Bölme değeri; örneğin, 1_000
  string söz;    // Değere karşılık gelen söz; örneğin, "bin"
}

// Parametresi, türün kaç bayttan oluştuğunu bildirir. O türün
// ifade edebildiği sözlerden oluşan bir dizi döndürür. Örneğin,
// 2 bayttan oluşan short türü ancak "bin" sözünü kullanır.
auto sözler(size_t türBüyüklüğü) {
  // Burada yalnızca hoşuma gittiği için özyineleme (recursion)
  // kullandım. Yoksa, unittest'e yazdığım dizi gibi burada da
  // her büyüklük için bir dizi yazabilirdim. Olsun; hoş... :)
  final switch (türBüyüklüğü) {
  case 1: return [ "" ];
  case 2: return sözler(1) ~ [ "bin" ];
  case 4: return sözler(2) ~ [ "milyon", "milyar" ];
  case 8: return sözler(4) ~ [ "trilyon", "katrilyon", "kentilyon" ];
  }
}

unittest {
  assert(sözler(4) == [ "", "bin", "milyon", "milyar" ]);
}

// Belirtilen türle ilgili olan Bölen!T dizisi döndürür.
auto bölenler(T)() {
  import std.range : array, enumerate;
  import std.algorithm : map;

  static const(Bölen!T[]) b = sözler(T.sizeof)
                              .enumerate!T
                              .map!(t => Bölen!T(cast(T)(10^^(t.index * 3)), t.value))
                              .array;
  return b;
}

unittest {
  // Bir kaç tane deneme yetsin:
  assert(bölenler!int[1] == Bölen!int(1_000, "bin"));
  assert(bölenler!ulong[3] == Bölen!ulong(1_000_000_000, "milyar"));
}

void main() {
  import std.meta : AliasSeq;
  import std.stdio : writefln;

  static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong)) {{
    // Her tür için 2-3 adet örnek yazdıralım
    static if (isSigned!T) {
      // Burada da 'T.min + 1' otomatik olarak int'e dönüştüğünden
      // bu deneme kodunda yine cast(T) yapıyorum.
      göster(cast(T)(T.min + 1));
    }
    // Aynı nedenden cast(T)
    göster(cast(T)(T.max / 2));
    göster(T.max);
  }}
}

void göster(T)(T değer) {
  import std.stdio : writefln;
  writefln!"  %6s % ,s: %s"(T.stringof, değer, sözle(değer));
}

Ali

January 24, 2022
On 1/23/22 18:04, Ali Çehreli wrote:

> Benim son programım aşağıda. T.min ile ilgili sorunum var çünkü yalnızca
> pozitif sayılarla işleyen işlevi -sayı değeriyle çağırıyorum.

O sorunu çözdüm. Sorun yaşadığım toplam 4 adet değer için işlevin açıklamalarına "T.min ile işleyemez" demeye bile değmez. :) sözle() işlevinin içine, çıktı.clear()'den sonraya şunu yerleştirdim:

  // Aşağıdaki algoritma işaretli sayıların T.min değeriyle çalışamaz
  // çünkü basitçe '-T.min' yaparak onun değerini yazdırmaya
  // çalışır. Ancak, işaretli türlerde '-T.min == T.min' ilişkisi
  // geçerli olduğundan o amaca ulaşamayız. O yüzden sorunlu değerleri
  // burada hallediyorouz.

  alias sorunluDeğerler = AliasSeq!(
    byte, "eksi 128",
    short, "eksi 32 bin 768",
    int, "eksi 2 milyar 147 milyon 483 bin 648",
    long, "eksi 9 kentilyon 223 katrilyon 372 trilyon 36 milyar 854 milyon 775 bin 808");

  static foreach (i, SorunluTür; sorunluDeğerler) {
    static if (i % 2) {
      // Bu, dizgi değeri; atlıyoruz

    } else {
      // Bu, sorunlu bir tür
      static if (is (T == SorunluTür)) {
        // Ve T bu türdenmiş
        if (sayıParametre == T.min) {
          // Ve tam da sorunlu değer isteniyormuş; işimiz kolay
          çıktı ~= sorunluDeğerler[i + 1];
          return çıktı.data;
        }
      }
    }
  }

D sağolsun, bütün o kodun etkisi, sorunlu türlerin şablon gerçekleştirmesine iki satır eklemektir. Örneğin, T int olduğunda şu dört satır eklenir:

  if (sayıParametre == int.min) {
    çıktı ~= "eksi 2 milyar 147 milyon 483 bin 648";
    return çıktı.data;
  }

T orada bulunmayan bir tür (örneğin, uint) olduğunda ise hiçbir satır eklenmez. Harika! :)

Ali

January 29, 2022

On Monday, 24 January 2022 at 16:54:34 UTC, Ali Çehreli wrote:

>

On 1/23/22 18:04, Ali Çehreli wrote:

>

Benim son programım aşağıda. T.min ile ilgili sorunum var
çünkü yalnızca
pozitif sayılarla işleyen işlevi -sayı değeriyle çağırıyorum.

O sorunu çözdüm. Sorun yaşadığım toplam 4 adet değer için işlevin açıklamalarına "T.min ile işleyemez" demeye bile değmez. :)

Harika olmuş hocam elinize sağlık. Siz özellikle yardımcı işlevler ile dilin tüm olanaklarını birlikte çok güzel kullanıyorsunuz. Peki sizin algoritma İngilizce ile uyumlu mu? Yani bazen A, bazen yazıyla ONE kullanırlar ya. Şurada kısa bir bilgi buldum: https://english.stackexchange.com/questions/437165/a-thousand-one-hundred-and-one

Bu arada hafta boyunca çok yoğundum; şimdi denemeye başladım da kod içinde 2 sorum daha var:

auto sözle(T)(T sayı) {
  // static // olmasının nedeni? kaldırdım bir şey olmadı!
  Appender!(char[]) çıktı;
  çıktı.clear;

  if (sayı == 0) {
    çıktı ~= " sıfır";
  } else {
    if (sayı < 0) {
      çıktı ~= " eksi";
      sayı = -sayı;
    }
    sözleImpl(sayı, çıktı);
  }
  return çıktı.data[1..$];//.strip;
} // std.string : strip bağından kurtulduk! pratik mi?

Bu kadar basit bir uygulamaya bile eğitim niteliğinde eklenebilecek çok şey var: Örneğin virgüllü sayılar! Ama sayenizde unuttuğum sıfır'ı return yanına üçlü işleç ile pratik şekilde kendi koduma ekledim.

Ayrıca sıradan LIFO mantığıyla çalışan bir stack ile yeni bir çözüm gerçekleştirdim. Eğer meydan okumaya 3. biri katılmazsa paylaşacağım.

Teşekkürler...

January 29, 2022
On 1/29/22 04:27, Salih Dincer wrote:

> algoritma İngilizce ile uyumlu mu? Yani bazen A, bazen yazıyla ONE
> kullanırlar ya.

Hayır, İngilizce'yle uyumlu değil çünkü bu algoritmanın hiçbir tarafı İngilice'yle uyumlu değil. :) İngilizce'de "2 thousand" bile olmaz, "two thousand" olur.

Ama bu programı başka bir konu nedeniyle yine de İngilizce'ye çevirdim ("1 bin" düzeltmesini kaldırarak) çünkü genel kanının aksine, en hızlı program gdc çıktı:

  https://forum.dlang.org/thread/sspkdp$1m4n$1@digitalmars.com

Sonuçta, bunun nedeninin elma ile armudu karşılaştırmamdan kaynaklandığı anlaşıldı. Sistemde ne derleyici varsa onları kullanıyordum. (Manjaro Linux; Arch tabanlı.) gdc, 2.076 döneminden kalmaymış. 2.076'daki formattedWrite da daha yeteneksiz olduğundan daha az işlem yapıyormuş ve o yüzden ldc'yi geçiyormuş.

Sonuçta formattedWrite yenrine bütün derleyicilerde aynı olacaklarını varsaydığım C'nin sprintf'ini kullanınca evet, ldc en hızlı derleyici çıktı ama bu sefer de dmd bile gdc'yi geçti! Onun nedeni de, gdc'nin en hızlı derlemesi için -O3 yanında başka seçenekler de gerekiyormuş. Başkası denedi ve gdc dmd'den biraz daha hızlı çıktı.

>    // static // olmasının nedeni? kaldırdım bir şey olmadı!
>    Appender!(char[]) çıktı;

static olmazsa, bu işleve her girildiğinde yeni bir değişken yaşamaya başlar. static olursa, tek değişken bu işlev tarafından paylaştırılır. Hatta, bu değişkenden her işlev parçacığında (thread) bir adet farklı olur. (Yani, 4 iş parçacığı varsa 4 adet değişken olur.

Orada amaç, her işlevde tazeden dinamik bellekten yer ayırmayı önlemek.

>    çıktı.clear;

O satırın amacı, bir önceki çağrının sonucundan kurtulmak için zaten ayrılmış olan alanı başa sarmak. (Tek göstergenin değerini değiştirmek kadar hızlı bir işlemdir.)

Ben bu konudan, performansı arttırma amacıyla şurada bahsetmiştim:

  https://www.youtube.com/watch?v=dRORNQIB2wA&t=785s

Ama dediğim gibi, daha hızlı çalışması için Appender da gitti ve bir static bellek için sprintf kullanıldı. (Yukarıdaki forum konusunda.)

> eklenebilecek çok
> şey var: Örneğin virgüllü sayılar!

Ama galiba kullandığımız düzende virgüle yer kalmıyor: "123 milyon 456 bin".

Ali

February 13, 2022

On Saturday, 29 January 2022 at 12:27:11 UTC, Salih Dincer wrote:

>

Ayrıca sıradan LIFO mantığıyla çalışan bir stack ile yeni bir çözüm gerçekleştirdim. Eğer meydan okumaya 3. biri katılmazsa paylaşacağım.

Belli ki bu güzel algoritma (-bknz. Wikipedia'da Short Scale konusu) fazla kişinin ilgisini çekmemiş olacak ki tarihin (pardon forumun!) tozlu sayfalarında kaybolmuş... 😀

Yabancı forumda 5 sayfa kadar devam etmişiz ama buraya da nakledeceğime söz vermiştim. Benim çözümüm aşağıdaki gibi iç içe kapsülenmiş 2 yapı ile kendine özgü basit bir yığından ibaret. Bazen dil olağını karıştırmadan böyle şeyler pratik olabiliyor:

struct ShortScale {
  struct ShortStack {
    short[] stack;
    size_t index;

    @property back() {
      return this.stack[0];
    }

    @property push(short data) {
      this.stack ~= data;
      this.index++;
    }

    @property pop() {
     return this.stack[--this.index];
    }
  }

  ShortStack stack;

  this(long i) {
    long s, t = i;

    for(long e = 3; e <= 18; e += 3) {
      s = 10^^e;
      stack.push = cast(short)((t % s) / (s/1000L));
      t -= t % s;
    }
    stack.push = cast(short)(t / s);
  }

  string toString() {
    string[] scale = [" sıfır", "bin", "milyon",
    "milyar", "trilyon", "katrilyon", "kentilyon"];

    import std.array : appender;
    import std.format : formattedWrite;

    auto r = appender!string;

    for(long e = 6; e > 0; e--) {
      auto t = stack.pop;
      if(t > 1) r.formattedWrite(" %s", t);
      else if(t && e != 1) r.formattedWrite(" bir");
      if(t) r.formattedWrite(" %s", scale[e]);
    }
    if(stack.back) r.formattedWrite(" %s", stack.back);

    return r.capacity ? r.data : scale[0];
  }
}

void main() {
  long[] inputs = [ 741, 1_500, 2_001, 5_005, 1_250_000,
                    3_000_042, 10_000_000, 1_000_000,
                    2_000_000, 100_000, 200_000, 10_000,
                    20_000, 1_000, 2_000, 74, 7, 0, 999,
                    1_999_999_999_999];

  import std.stdio;

  foreach(long i; inputs) {
    auto OUT = ShortScale(i);
    auto STR = OUT.toString[1..$];
    writefln!"%s"(STR);
  }
} /* ÇIKTISI:
741
bin 500
2 bin 1
5 bin 5
bir milyon 250 bin
3 milyon 42
10 milyon
bir milyon
2 milyon
100 bin
200 bin
10 bin
20 bin
bin
2 bin
74
7
sıfır
999
1 trilyon 999 milyar 999 milyon 999 bin 999
*/

Hızı ise Ali hocanın çözümünden yavaş olabilir. Çünkü o başka dil olanakları kullandı. Ayrıca negatif sayıları desteklemiyor. Zaten bu meydan okumada istenen bir şey değildi, şunun dışında:

1500'ün Türkçe: bin 500, İngilizce: one thousand 500 (başka aksanlarda a thousand 500) olarak yazılması. Ayrıntılı bilgi için 9. bölüm, sayfa 26:

https://www.yumpu.com/tr/document/read/13809959/1-ses-harf-ve-alfabe

Başarılar...