Thread overview
"Going to Boston" zar oyununun istatistiği
Mar 02, 2020
Salih Dinçer
Mar 02, 2020
Salih Dinçer
February 26, 2020

Zar atarak oynanan oyunları merak edip "Going to Boston" diye bir oyun bulduk.

Her oyuncu için:

  • Üç zar atın, en büyüğünü seçin (örnek: 2,5,1; seçilen: 5)
  • İki zar atın, en büyüğünü seçin (örnek: 6,6; seçilen: 6)
  • Tek zar atın, en büyüğünü seçin (örnek: 3; seçilen: 3)
  • Hepsini toplayın; 5 + 6 + 3 = 14

Hangi oyuncunun toplamı daha büyükse o kazanır.

Hangi toplamdan büyük değerlerin kazanma şansının daha fazla olduğunu merak ettim. Hesaplayarak çözülebilmesine rağmen tembellik ettim ve bu oyunu deneyen bir program yazmaya karar verdim. İlk yazdığımda program yalnızca 20-30 satırdan ve yalnızca main() işlevinden oluşuyordu. Sonradan güzelleştirince ve açıklamalar ekleyince gereğinden fazla büyüdü. :/

Not: Hesaplama örneği olarak, toplam 3 gelmesi için ilk üç zarın hepsinin de 1 gelmesi, sonraki iki zarın ikisinin de 1 gelmesi ve son tek zarın da 1 gelmesi gerek. Böylece üç atışın da en büyüğü 1 oluyor ve toplam 3 ediyor. Bunun olasılığı, 6 zarın 1 gelme olasılığına eşit: 1/(6^^6) = 0.0000214335 = %0.0021 = yüz binde iki...

Birisinin ilgisini çekebilir diye buraya koymaya karar verdim.

Komut satırında belirtilen sayıda oyun oynatıyor ve hangi değerin kaç kere çıktığının histogramını gösteriyor:
'
$ ./deneme 100000
1: ⚁ ⚅ ⚃ = 12
2: ⚄ ⚄ ⚄ = 15
3: ⚁ ⚅ ⚀ = 9
4: ⚅ ⚃ ⚁ = 12
5: ⚃ ⚄ ⚂ = 12
6: ⚄ ⚅ ⚂ = 14
7: ⚄ ⚄ ⚄ = 15
8: ⚃ ⚄ ⚀ = 10
9: ⚅ ⚃ ⚂ = 13
10: ⚅ ⚄ ⚁ = 13
[...]

Değer Adet Yüzde YüzdeToplamı

3: 2 0.00% 0.00%
4: 31 0.03% 0.03%
5: 110 0.11% 0.14%
6: 375 0.38% 0.52% #
7: 1,069 1.07% 1.59% #####
8: 2,569 2.57% 4.16% #############
9: 4,820 4.82% 8.98% ########################
10: 7,762 7.76% 16.74% ########################################
11: 11,217 11.22% 27.95% #########################################################
12: 14,160 14.16% 42.11% ########################################################################
13: 15,520 15.52% 57.63% ################################################################################
14: 14,135 14.13% 71.77% ########################################################################
15: 12,022 12.02% 83.79% #############################################################
16: 8,769 8.77% 92.56% #############################################
17: 5,285 5.29% 97.85% ###########################
18: 2,154 2.15% 100.00% ###########
'
Anlaşılan, kazanma şansının büyük olması için 13'ten büyük değer yakalamak gerekiyor.

Program şöyle:

import std.range;
import std.algorithm;
import std.string;
import std.random;
import std.stdio;
import std.format;
import std.conv;

// "Going to Boston" oyununun kazanma olasılıklarını gösterir.

enum varsayılanDeneyAdedi = 10_000;

enum enKüçükZar = 1;
enum enBüyükZar = 6;

enum toplamZar = 3;

enum enKüçükSonuç = toplamZar * enKüçükZar;
enum enBüyükSonuç = toplamZar * enBüyükZar;

enum farklıSonuçAdedi = enBüyükSonuç - enKüçükSonuç + 1;

// Belirtilen sayıda zar atar ve en büyük olanını döndürür.
int atış(size_t adet)
in (adet >= 1)
out (sonuç; ((sonuç >= enKüçükZar) &&
            (sonuç <= enBüyükZar)))
{
 return iota(adet)
   .map!(i => uniform(enKüçükZar, enBüyükZar + 1))
   .array    // <-- Buradaki .array, doğruluk açısından gerekli çünkü
             //     maxElement elemanları karşılaştırırken *aynı*
             //     eleman için uniform()'u dolaylı olarak birden
             //     fazla kere çağırmak zorunda kaldığından farklı
             //     değerler görebiliyor.
   .maxElement;
}

// Bir el oynatır; yani, art arda üç, iki, ve tek zar atar. Her
// atıştaki en büyük değerden oluşan dizi döndürür.
int[] el()
// Not: Buradaki cast(), sonuç'un 'const'ını kaldırmak için
// gerekti. Çünkü, Phobos'un bir hatası olarak SortedRange.length
// const nesneler üzerinde derlenemiyor.
out (sonuç; (cast()sonuç).length == toplamZar)
{
 return iota(toplamZar, 0, -1)
   .map!(adet => atış(adet))
   .array;
}

struct Sonuç {
 size_t numara;     // Deney numarası
 int değer;         // Elin değeri
 string görüntü;    // Elin görsel gösterimi

 this(size_t indeks, int[] el) {
   static const görüntüler = [ "⚀", "⚁", "⚂", "⚃", "⚄", "⚅" ];

   this.numara = indeks + 1;
   this.değer = el.sum;
   this.görüntü = format!"%-( %s%)"(el.map!(zar => görüntüler[zar - 1]));
 }

 void toString(scope void delegate(const(char)[]) çıktı) {
   formattedWrite(çıktı, "%7,s:%2s = %2s", numara, görüntü, değer);
 }
}

// Belirtilen sayıda deney işletir ve sonuçlarını bir dizi olarak
// döndürür.
auto dene(size_t adet) {
 return iota(adet)
   .map!(i => Sonuç(i, el()))
   .array;
}

// Histogramdaki eleman adedinin doğru olduğunu denetler.
bool uzunluğuDoğru(const size_t[int] histogram) {
 return ((histogram.length >= 1) &&
         (histogram.length <= farklıSonuçAdedi));
}

// Her el sonucundan kaç adet çıktığını sayar.
size_t[int] say(R)(R deneyler)
out (sonuç; ((deneyler.empty && sonuç.empty) ||
            (!deneyler.empty && sonuç.uzunluğuDoğru)))
{
 typeof(return) histogram;

 deneyler
   .map!(t => t.değer)
   .each!(sonuç => histogram[sonuç]++);

 return histogram;
}

void bilgiVer(const size_t[int] histogram)
in (histogram.uzunluğuDoğru)
{
 const başlık = "Değer    Adet  Yüzde YüzdeToplamı";
 writefln!"\n%s\n%s"(başlık, "-".replicate(başlık.walkLength));

 const toplamDeney = histogram.byValue.sum;
 const enBüyükAdet = histogram.byValue.maxElement;

 double yüzdeToplamı = 0;

 string bilgiSatırı(int sonuç) {
   const yüzde = histogram[sonuç] / double(toplamDeney) * 100;
   yüzdeToplamı += yüzde;
   enum enBüyükÇubukUzunluğu = 80;
   const uzunluk = histogram[sonuç] * enBüyükÇubukUzunluğu / enBüyükAdet;
   const grafikÇubuğu = "#".replicate(uzunluk);
   return format!"%2s:%9,s %6.2f%% %6.2f%% %s"(
     sonuç, histogram[sonuç], yüzde, yüzdeToplamı, grafikÇubuğu);
 }

 writefln!"%-(%s\n%)"(histogram
                      .keys
                      .sort
                      .map!(sonuç => bilgiSatırı(sonuç)));
}

void main(string[] args) {
 const adet = {
   if (args.length == 1) {
     writefln!"Deney adedi verilmedi; %,s varsayıyorum."(varsayılanDeneyAdedi);
     return varsayılanDeneyAdedi;

   } else {
     return args[1].to!size_t;
   }
 }();

 auto sonuçlar = dene(adet);

 // Yalnızca ilk bir kaç deney sonucunu göstermekle yetiniyoruz
 writefln!"%-(%s\n%)"(sonuçlar.take(10));
 writeln("[...]");

 const histogram = say(sonuçlar);
 bilgiVer(histogram);
}

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

February 26, 2020

Bahsettiğim SortedRange.length hatasıyla ilgili sayfa şu:

https://issues.dlang.org/show_bug.cgi?id=9792

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

February 27, 2020

Her programda olduğu gibi, burada da eniyileştirme fırsatları görüyoruz:

  • .array çağrısı dizi oluşturduğundan çoğu programda en yavaş işlemler arasındadır. Özellikle aralıklarla kullanıldığında, aralıkların getirdiği tembelliğin yararını ortadan kaldırır ve işlemleri her eleman için teker teker işleterek dizi oluşturur.

  • Oyunun tanımı gereği, her elde 3 + 2 + 1 = 6 kere zar atmak gerekiyormuş gibi görünse de, aslında işlem sayısını azaltabiliriz: Örneğin, 3 zar atıldığı durumda ilk zar 6 geldiğinde sonrakileri atmaya gerek yoktur çünkü 3 zarın en büyüğünün 6 geleceği bellidir. Bu eniyileştirmeyi uygulayınca her el için ortalamada 5.3 kere zar atmak yetiyor (6 yerine).

  • Yalnızca 3'ten 18'e kadar anahtar değerleri olduğuna göre histogramda 'associative array' kullanmak yerine dizi kullanmak yeter.

Bu yöntemleri uygulayınca (ve dmd -O ile derleyince) program bende 6 kat hızlı oldu. Hepsini göstermeye gerek yok ama attış() şablon (template) haline geldi ve el() şöyle değişti:

// Belirtilen sayıda zar atar ve en büyük olanını döndürür.
int atış(size_t n)() {
 int a = uniform(enKüçükZar, enBüyükZar + 1);

 static if (n == 1) {
   return a;

 } else {
   if (a == enBüyükZar) {
     return a;
   }

   return max(a, atış!(n - 1)());
 }
}

// Bir el oynatır; yani, art arda üç, iki, ve tek zar atar. Her
// atıştaki en büyük değerden oluşan toplamı döndürür.
int el() {
 return atış!3() + atış!2() + atış!1();
}

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 02, 2020

Sabah işe giderken oyunun kurallarını (üçlü ve ikili zar atıp en büyüklerini tekli başka bir zar ile topla) ve kodun içeriğini okudum; çok leziz görünüyorlar. Performans arttırımı için minimalist çözüm olup olmadığını biraz önce düşündüm ve bir çırpıda çekirdek kısmı yazdım:

// D 2.0.83

import std.stdio;
import std.random;

int[] zarAt(){
 int[] zar = [ uniform(1, 7),
                uniform(1, 7),
                uniform(1, 7) ];
 int s;

 if(zar[2] >= zar[1]) s = zar[2];
 else s = zar[1];
 foreach (z; zar) z.write(" ");
 return [ zar[0], s];
}

void main()
{
 auto zar1 = zarAt(); // tekli + üçlünün ikisinin en büyüğü
 auto zar2 = zarAt(); // üçlünün teki + ikilinin en büyüğü

 if(zar2[0] > zar1[1]) {
   zar1[1] = zar2[0];
 }
 zar1[1] += zar2[1] + zar1[0];

 writeln(" : ", zar1[1]);
}/* 3 farklı çıktı örneği:
 6 3 5 6 5 3  : 17
 1 3 6 1 1 6  : 13
 5 2 4 4 2 3  : 12
 */

Biliyorum çok karışık görünecek çünkü oyunun sıralamasını gözetmeden ama 6 zar atar gibi yaptım. Kesişim kümelerini de basitçe karşılaştırıp topladım. Bence çalışıyor...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 02, 2020

Bin oyunun yaklaşık %90'ı:
18: 19 adet, yani %1.9
17: 58 adet, yani %5.8
16: 88 adet, yani %8.8
15: 121 adet, yani %12.1
14: 127 adet, yani %12.7
13: 156 adet, yani %15.6
12: 139 adet, yani %13.9
11: 107 adet, yani %10.7
10: 100 adet, yani %10
..
oluşturacak şekilde sonuç (ilk binde hiç 3 ve 4 çıkmazken 5 sadece 1 adet) elde ettim. Elbette yeterince deneme ile diğer olasılıklar da çıkacaktı. Peki üçlü bir atışta en fazla 9 gelebilecek imkansız bir sanal zarımız olsaydı, 1 milyon oyunda sonuç şöyle olurdu:
Alıntı:

>

9: 130415 adet, yani %13.0415
8: 256988 adet, yani %25.6988
7: 317159 adet, yani %31.7159
6: 201820 adet, yani %20.182
5: 77176 adet, yani %7.7176
4: 15069 adet, yani %1.5069
3: 1373 adet, yani %0.1373

Hocamın sorusunun yanıtına geçersek; an itibariyle veriye daha kolay ve az vakit harcama dışında kazandırdığı tek şey: algoritma :)

Evet, algoritmanın kendisi inanabiliyor musunuz! Ben yapay zeka (AI) üzerine çalışıyorum ve bu benim için değerli bir örnek (okulda ilk öğretilen büyük/küçük sayı karşılaştırması kadar basit ama içerisinden muazzam bilgi çıkarılabilen) oldu.

Az işlemci gücü harcayarak yazdığım algoritmanın bir benzerini AI'dan istediğinizde; kendi kodunu yazan bir makine öğrenmesi esnasında, vereceğiniz oyun kurallarından sade bir kod üretemiyor...

İlk yaptığı aptalca rasgele sayılar üretmek. Sonra bunlardan aklınızın alamayacağı büyük bir yığın istatistiki veri (Big Data) elde ediyor ama bir Karnough Map gibi bir ilişkilendirme ve sadeleştirme yapamıyor. Klasik prime implicant sorunu işte!

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 02, 2020

Güzel: Sonuçta aynı işi yapıyor ve aynı olasılıkları üretiyor. Peki, bu karışıklığın kazandırdığı bir şey var mı? :)

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]