Merhaba,
Yazılım olgusu, bilgisayara kullanıcının isteği doğrultusunda algoritmik bir senfoni eşliğinde komutlar ile taleplerde bulunulan ve bu taleplere karşı verilen cevaplara yahut olayların gidişatı kontrolünde beklenen durumlara göre süreci amaca uygun bir şekilde işleterek bir veriyi doğru ve beklendiği şekilde işleyebilme durumudur. Evet, olaya biraz kuramsal yaklaşmak istemiş olabilirim. Ama bu yaklaşımın ardından bilgisayar denen aygıtın hala günümüzde rastgele bir sayı üretecek kadar gelişmemiş olması gerçeği ise bu girişi sanırım oldukça trajikomik bir hale getirmektedir.
Yazılımlar insanların yahut belirli makinelerin ihtiyaçlarına cevap verebilecek donanımı kendilerinde dolaylı yoldan barındırabilirken, bu yazılımı kullanan ve kendine o anlık zihinsel karar mekanizması olarak tanıyan bilgisayar denen aygıt nasıl oluyor da bir rastgele sayı üretebilecek kapasitede olamıyor şaşılacak şey değil!
Evet, piyasada dolanan sorulara istinaden kaleme(doğrusu klavyeye) almış olduğum bu makalemizde rastgele sayı üretimi üzerine hala fikir birliğine varılamayan birkaç husus üzerine elden geldiğince aydınlatıcı bir teori ve açıklamada bulunacağım…
Tabi öncelikle bir bilgisayarda, hazır Random sınıfı olmasaydı nasıl rastgele sayı üretebiliriz bu durum üzerine istişare etmekte fayda görüyorum.
Şimdilik aklıma iki ama etkili yöntem gelmektedir.
Bunlardan birincisi, rastgele üretilecek sayıları önceden bir koleksiyon içerisinde tutarak istendiği zaman bunlardan herhangi birini istemciye göndermek olabilirdi. Biliyorum çok vasat ama etkili ve net çözüm 🙂
İkinci yöntem ise, bağıntısı oluşturulduğu zaman muhtemelen bir benzer sonucu üretemeyecek bilgisayarın tarihsel verileri üzerinden yapılacak çalışmalar neticesinde üretilen sayılardır. Örneğin; bu günün tarihi verilerinden, saat ve milisaniye çarpım sonucunu yıla bölerek çıkan sonuca ay değerinin 3/4 alıp, karesini eklersek eğer karşımıza çıkan sonucun bir saniye sonra bu işlemin aynısını yaptığımızda çıkan sonuçla dağlar kadar farklı olduğunu görebiliriz. İşte size sonsuza denk farklı sonuc üretecek bir algoritma.
Zaten .NET mimarisinde ki Random sınıfıda sistemdeki tarih bilgilerini kullanarak ortaya rastgele bir sayı üretmektedir. İşte tüm meselenin sırrı budur. Hangi meselenin hoca la? Birazdan piyasada dolaşan Random sayı üretimiyle ilgili sıkıntıları masaya yatıracağız. Dolayısıyla bu sıkıntılardan birinin kaynağı Random sınıfının sistemdeki tarih bilgisini kullanmasından kaynaklandığını peşinen belirtiyorum.
Velhasıl madem lafını etmişken ilgili sıkıntıyı ilk olarak ele alalım.
#Soru 1#
Random Sınıfı Sürekli Aynı Sayıyı Üretiyor
Bu sorunun temel kaynağı yukarıda da belirttiğim gibi Random sınıfının sistemdeki tarih bilgisini kullanıyor olmasıdır. Bir diğer kaynağı ise Random sınıfının kullanım tarzıdır. Şimdi bu durumları programatik olarak ele alacağız;
int counter = 1;
while (counter < 100)
{
Random rst = new Random();
Console.WriteLine(rst.Next());
counter++;
}
Yukarıdaki kod bloğunun ekran çıktısı aşağıdaki gibi olacaktır.
Evet… Kodu ve neticeyi incelersek eğer görünen o ki beklenen sonuç elde edilememiş ve üretilen sayılar birbirlerini tekrar eden cinsten artık rastgele değil tahmin edilebilir bir vaziyette elde edilir olmuş.
Yukarıda bahsettiğim gibi Random sınıfı tarih bilgilerini kullanarak bir değer üretmektedir. Dolayısıyla bu işlemi constructer metoduyla gerçekleştirmektedir. Eğer ki Random sınıfının parametresiz constructerını kullanırsanız ilgili sınıf direkt olarak sistem zamanını değerlendirecek ve tarihsel bilgilerden bir bağıntı ile rastgele sayı üretecektir. Ama burada while döngüsü zamandan oldukça hızlı çalıştığından dolayı sürekli aynı zaman üzerine odaklanılacak ve o an ki milisaniye değeri değişene denk aynı sonuç üretilecektir.
Soruna sebep olan diğer durum ise Random sınıfını kullanım tarzıdır. Dolayısıyla yukarıdaki kullanım tarzı her bir döngüde yeni bir Random nesnesi talep edecek ve izahatte bulunduğumuz hataya sebebiyet verecektir. O halde algoritma inşasını aşağıdaki gibi düzeltmemiz hatayı ortadan kaldıracaktır.
int counter = 1;
Random rst = new Random();
while (counter < 100)
{
Console.WriteLine(rst.Next());
counter++;
}
En nihayetinde Random sınıfını bir kere üretip ardından döngü ile var olan nesne üzerinden rastgele sayı talebinde bulunursak eğer her bir döngü için farklı bir değer üretilecektir.
Elde edilen sonuç;
Buradan çıkaracağımız sonuç Random sınıfının başlangıç değeri olarak neyi kabul ettiğidir. Eğer ki, döngü dışında üretilen Random nesnesine constructer aracılığıyla bir seed değeri verilirse sanki döngü içerisinde sürekli aynı zaman değerlerinde üretilen Random nesnesiymiş gibi yine aynı hatayla karşılaşılacaktır. Ama bu sefer üretilen değerler farklı olsada her periyotta aynı olacaktır.
Anlatmaya çalıştığımı örneklendirmek için;
int counter = 1;
Random rst = new Random(1111);
while (counter < 10)
{
Console.WriteLine(rst.Next() + " ");
counter++;
}
komutunu iki kere çalıştırmamız yeterlidir.
Bu şekilde bir kullanım ile ard arda çalıştırmalarda sürekli aynı sonuçları aldığımızı görüyoruz.
| 1. Çalıştırma | 2. Çalıştırma |
|---|---|
|
302295548 1729603735 738047416 1488758225 1732353655 986442359 1496834943 1747084034 916554424 |
302295548 1729603735 738047416 1488758225 1732353655 986442359 1496834943 1747084034 916554424 |
Neticede Random sınıfı, aslında rastgele değerleri sade ve sadece parametresiz constructerı çalıştırırken ürettiğini söyleyebiliriz. Gerisi, üretilen ilk sayıya ucundan kıyısından bir oranda bağlı olarak üretilen sayılardır diyebiliriz. Bunun dışında parametre olarak seed değeri verildiğinde üretilen sayılar kanımca sıkı ve dikkatli bir takiple tahmin edilebilir olduğu kanaatindeyim.
#Soru 2#
Tekrarsız Rastgele Sayı Üretmek İstiyorum
Aslında basit bir mantıkta çözüm üretebileceğimiz bir sorundur. Yapılması gereken üretilen değerleri bir koleksiyonda toplamak ve önceki değerler arasında varsa koleksiyona eklemeksizin yerine yeni değer üretip aynı kontrol eşliğinde tekrar koleksiyona atmak olacaktır.
Tabi bu işlemi yaparken yöntem olarak doğru seçimde bulunmak oldukça kritik arz etmektedir. Nihayetinde milyarlarca sayı için bu işlemi yaptığınızı düşünürseniz performans ve maliyet açısından lüzumsuz yere bonkör davranmanız söz konusu olabilir.
Bu tarz işlemlerde HashSet koleksiyon tipini kullanmanız en doğrusu olacaktır. HashSet koleksiyonunun kullandığı kovalama yöntemi sayesinde daha performanslı bir şekilde bu işlemi gerçekleştirebiliriz. Ayriyetten ilgili koleksiyon ISet interfaceini implement ettiğinden dolayı yeni üretilen değeri koleksiyondaki önceki değerlerle kıyaslama gibi bir derdimiz kalmayacaktır. Nihayetinde ISet interfaceini uygulayan koleksiyonlarda verile unique’dir ya da bir başka deyişle aynı elemandan birden fazla bulunmayacaktır. Eklenmeye çalışılan eleman koleksiyonda mevcutsa eğer Add metodu false değeri döndürecektir.
Şimdi gelin bir normal koleksiyon ile bir de HashSet koleksiyonu ile buradaki ihtiyaca dönük işlevlerimizi gerçekleştirelim.
Normal koleksiyon ile;
int counter = 1;
List<int> sayilar = new List<int>();
Random rst = new Random();
while (counter < 100)
{
int rstSayi = rst.Next();
if (!sayilar.Contains(rstSayi))
sayilar.Add(rstSayi);
counter++;
}
HashSet ile;
int counter = 1;
HashSet<int> sayilar = new HashSet<int>();
Random rst = new Random();
while (counter < 100)
{
sayilar.Add(rst.Next());
counter++;
}
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
