10 Rastgele Sayılar: random Modülü
Zar atan bir oyun, bir sınıftan rastgele öğrenci seçen bir program ya da bir olayın olasılığını binlerce deneme yaparak tahmin eden bir simülasyon yazmak için bilgisayarın rastgele sayılar üretebilmesi gerekir. Oysa bilgisayar her komutu kesin kurallarla yürüten bir makinedir; gerçek anlamda “rastgele” bir şey yapamaz.
Python bu sorunu, rastgele görünen ama aslında belirli bir hesapla üretilen sayılarla çözer. Bu bölümde bu sayıları üreten random modülünü tanıyacağız: sayı üretmeyi, bir listeden rastgele seçim yapmayı ve bir listeyi karıştırmayı göreceğiz. Ayrıca tohum değeri sayesinde “rastgele” sonuçları nasıl tekrar elde edebileceğimizi öğreneceğiz.
10.1 Sözde Rastgele Sayılar
Bilgisayarın ürettiği rastgele sayılar bir başlangıç değerinden belirli bir formülle hesaplanır.
Tanım 10.1 (Sözde Rastgele Sayı) Bir başlangıç değerinden belirli bir algoritmayla hesaplanan, ama istatistiksel olarak rastgele sayılardan ayırt edilemeyecek kadar düzensiz görünen sayılara sözde rastgele sayılar (pseudo-random numbers) denir. Bu sayıları üreten algoritmaya rastgele sayı üreteci denir.
Yani random modülünün verdiği sayılar gerçek bir zar atışı gibi öngörülemez değildir; başlangıç değeri bilinirse bütün dizi yeniden hesaplanabilir. Oyunlar, simülasyonlar ve istatistik deneyleri için bu yeterlidir. Şifre ya da güvenlik anahtarı üretmek gibi öngörülemezliğin önemli olduğu işler için ise Python’ın ayrı bir modülü (secrets) kullanılır.
Modül, öteki modüller gibi import random ile programa dahil edilir; içindeki fonksiyonlar random.fonksiyon_adi(...) biçiminde çağrılır. Alt çizgiyle başlamayan adlar şunlardır:
import random
print([ad for ad in dir(random) if not ad.startswith("_")])['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST', 'SystemRandom', 'TWOPI', 'betavariate', 'binomialvariate', 'choice', 'choices', 'expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate', 'lognormvariate', 'normalvariate', 'paretovariate', 'randbytes', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform', 'vonmisesvariate', 'weibullvariate']
Bu listedeki büyük harfli adlar modülün iç sabitleri ve sınıflarıdır; biz küçük harfli fonksiyonlardan en sık kullanılanlarını göreceğiz.
10.2 Tohum Değeri
Bir rastgele sayı üretecinin ürettiği dizi tamamen başlangıç değerine bağlıdır; bu değeri kendimiz verebiliriz.
Tanım 10.2 (Tohum Değeri) Rastgele sayı üretecinin başlangıç değerine tohum (seed) denir ve random.seed(a) ile belirlenir. Aynı tohumla başlatılan üreteç her seferinde aynı sayı dizisini üretir. Tohum verilmezse Python, işletim sisteminden ya da saatten alınan, her çalıştırmada farklı bir başlangıç değeri kullanır.
Yani tohum, “rastgele” sonuçların tekrar elde edilebilmesini sağlar: tohumu \(42\) olan bir program, kim nerede çalıştırırsa çalıştırsın aynı sayıları üretir. Bu, bir programı test ederken, bir simülasyonun sonucunu başkasıyla paylaşırken ya da bir oyunun bir bölümünü hep aynı koşullarla başlatırken çok kullanışlıdır.
import random
random.seed(1)
print(random.random())
print(random.random())
random.seed(1)
print(random.random())
random.seed(2)
print(random.random())0.13436424411240122
0.8474337369372327
0.13436424411240122
0.9560342718892494
Tohum \(1\) yeniden verildiğinde üreteç başa döndü ve aynı sayıyı yeniden üretti. Tohum verildikten sonraki ikinci sayı ise birinciden farklıdır: tohum dizinin başlangıcını belirler, her çağrı dizide bir adım ilerler.
Bu bölümdeki programların çoğu bir tohum değeriyle başlar; çıktılar bu tohumlarla ve Python 3.12 ile elde edilmiştir. Kendi bilgisayarınızda aynı tohumla aynı sonuçları alırsınız. random.seed(...) satırı silinirse sonuçlar her çalıştırmada değişir.
10.3 Sayı Üretmek
random modülü hem ondalıklı hem tam sayı üreten fonksiyonlar içerir.
| Fonksiyon | Ürettiği değer |
|---|---|
random.random() |
\([0, 1)\) aralığında ondalıklı bir sayı; parametre almaz |
random.uniform(a, b) |
\(a\) ile \(b\) arasında ondalıklı bir sayı |
random.randint(a, b) |
\(a\) ile \(b\) arasında, iki uç da dahil, bir tam sayı |
random.randrange(bas, bit, adim) |
range(bas, bit, adim) sayılarından biri; bit dahil değil |
randint ile randrange arasındaki farka dikkat etmek gerekir: randint(1, 6) \(6\)’yı da üretebilir, randrange(1, 6) üretemez; çünkü randrange, range gibi bitişi dahil etmez.
import random
random.seed(42)
print(random.random())
print(random.uniform(5, 10))
print(random.randint(1, 6))
print(random.randrange(0, 100, 5))0.6394267984578837
5.125053776113335
3
35
randrange(0, 100, 5) yalnız \(0, 5, 10, \ldots, 95\) sayılarından birini verir; bu yüzden sonuç her zaman \(5\)’in katıdır.
Örnek 10.1 (Zar Atmak) Bir zarı \(20\) kez atıp sonuçları bir listede toplayınız ve her yüzün kaç kez geldiğini sayınız.
Çözüm
Her atış için random.randint(1, 6) kullanırız; iki uç dahil olduğu için \(1\)’den \(6\)’ya kadar her yüz gelebilir. Atışları bir liste üreteciyle toplar, sonra her yüz için count ile sayarız. Döngü değişkeni kullanılmadığından adı _’dir.
import random
random.seed(7)
atislar = [random.randint(1, 6) for _ in range(20)]
print(atislar)
for yuz in range(1, 7):
print(yuz, ":", atislar.count(yuz))[3, 2, 4, 6, 1, 1, 5, 1, 3, 5, 1, 5, 2, 1, 1, 4, 4, 1, 2, 1]
1 : 8
2 : 3
3 : 2
4 : 3
5 : 3
6 : 1
\(20\) atışta her yüzün ortalama \(20/6 \approx 3{,}3\) kez gelmesi beklenir; az sayıda atışta sayıların bundan sapması doğaldır. Atış sayısı arttıkça her yüzün oranı \(1/6\)’ya yaklaşır. \(\blacksquare\)
10.4 Rastgele Seçim ve Karıştırma
Sayı üretmenin yanında, var olan bir koleksiyondan rastgele eleman seçmek ya da elemanlarının sırasını karıştırmak da sık yapılan işlerdir.
| Fonksiyon | Yaptığı iş |
|---|---|
random.choice(seq) |
seq dizisinden (liste, demet, metin, range) rastgele bir eleman seçer |
random.choices(seq, weights=..., k=n) |
seq’ten n eleman seçer; aynı eleman tekrar seçilebilir; weights her elemanın seçilme ağırlığıdır |
random.sample(seq, k) |
seq’ten k farklı eleman seçer ve yeni bir liste olarak verir |
random.shuffle(liste) |
listenin elemanlarını yerinde karıştırır; yeni liste vermez |
choices ile sample arasındaki fark, torbadan top çekme deneylerindeki farka karşılık gelir.
Tanım 10.3 (İadeli ve İadesiz Seçim) Bir topluluktan art arda seçim yapılırken seçilen eleman her seferinde topluluğa geri konuyorsa iadeli, geri konmuyorsa iadesiz seçim yapılmış olur. random.choices iadeli, random.sample iadesiz seçim yapar.
Yani choices ile aynı eleman birden çok kez seçilebilir; sample ile seçilen elemanların hepsi farklıdır ve k topluluğun eleman sayısından büyük olamaz.
import random
random.seed(3)
meyveler = ["elma", "armut", "muz", "kiraz"]
print(random.choice(meyveler))
print(random.choice("Ahmet"))
print(random.choice(range(1, 110, 5)))
print(random.choices(meyveler, k=5))
print(random.sample(meyveler, 3))armut
t
86
['elma', 'kiraz', 'armut', 'muz', 'muz']
['kiraz', 'armut', 'elma']
choices ile seçilen beş meyvede aynı meyve birden çok kez görünebilir; sample ile seçilen üç meyve ise birbirinden farklıdır.
weights parametresi elemanların seçilme olasılıklarını değiştirir. weights=[2, 1, 1] verildiğinde birinci elemanın seçilme olasılığı \(2/4 = 1/2\), diğerlerininki \(1/4\) olur.
import random
random.seed(5)
meyveler = ["elma", "armut", "muz"]
secim = random.choices(meyveler, weights=[2, 1, 1], k=1000)
for meyve in meyveler:
print(meyve, secim.count(meyve))elma 521
armut 240
muz 239
\(1000\) seçimde elmanın yaklaşık \(500\), diğerlerinin yaklaşık \(250\) kez seçilmesi beklenir; sayılar bu değerlere yakındır.
shuffle ve sample
Bir listeyi karıştırmanın iki yolu vardır ve aralarındaki fark, asıl listenin korunup korunmadığıdır.
random.shuffle(liste) listenin kendisini değiştirir; liste metotları gibi (bkz. Koleksiyonlar ve Listeler) geriye bir değer vermez. Asıl listenin bozulmaması isteniyorsa random.sample(liste, len(liste)) kullanılır: listenin bütün elemanlarını iadesiz seçmek, karışık sırada yeni bir liste verir.
import random
random.seed(10)
deste = ["Maça As", "Kupa Kız", "Sinek Vale", "Karo 10"]
random.shuffle(deste)
print(deste)
orijinal = [1, 2, 3, 4, 5]
karisik = random.sample(orijinal, len(orijinal))
print(karisik)
print(orijinal)['Karo 10', 'Sinek Vale', 'Kupa Kız', 'Maça As']
[5, 1, 4, 2, 3]
[1, 2, 3, 4, 5]
10.5 Dağılımlardan Sayı Üretmek
random.uniform bir aralıktaki bütün sayıları eşit olasılıkla üretir; bazı uygulamalarda ise değerlerin bir ortalama etrafında toplanması istenir.
random.gauss(mu, sigma) ve random.normalvariate(mu, sigma) fonksiyonları ortalaması mu, standart sapması sigma olan normal dağılımdan sayı üretir: üretilen sayıların çoğu mu’ya yakındır, mu’dan uzaklaştıkça sayılar seyrekleşir. İki fonksiyon aynı dağılımdan sayı üretir ama farklı yöntemler kullandığından aynı tohumla farklı diziler verir. Normal dağılımın kendisi Olasılık Teorisi notlarının konusudur.
import random
random.seed(42)
liste1 = [random.normalvariate(5, 10) for _ in range(6)]
print([round(x, 2) for x in liste1])
random.seed(42)
liste2 = [random.gauss(5, 10) for _ in range(6)]
print([round(x, 2) for x in liste2])[7.45, 0.03, 17.55, 3.62, -4.76, 10.65]
[3.56, 3.27, 3.89, 12.02, 3.72, -9.97]
Örnek 10.2 (Not Simülasyonu) Ortalaması \(70\), standart sapması \(12\) olan normal dağılımdan \(1000\) not üretiniz. Notları tam sayıya yuvarlayıp \(0\)–\(100\) aralığına sınırlayınız ve ortalamayı, en yüksek ve en düşük notu yazdırınız.
Çözüm
Her notu random.gauss(70, 12) ile üretir, round ile yuvarlarız. Normal dağılım ender de olsa \(100\)’den büyük ya da \(0\)’dan küçük değerler üretebilir; bunları sınırlamak için max(0, min(100, n)) yazarız: min(100, n) \(100\)’ü aşan değeri \(100\)’e indirir, max(0, ...) da negatif değeri \(0\)’a çıkarır.
import random
random.seed(2026)
notlar = [random.gauss(70, 12) for _ in range(1000)]
notlar = [max(0, min(100, round(n))) for n in notlar]
print(f"Ortalama: {sum(notlar) / len(notlar):.2f}")
print("En yüksek:", max(notlar), "- En düşük:", min(notlar))
print("İlk 10 not:", notlar[:10])Ortalama: 70.08
En yüksek: 100 - En düşük: 25
İlk 10 not: [80, 80, 46, 68, 77, 75, 58, 61, 73, 55]
Ortalama \(70\)’e çok yakın çıkar. Normal dağılımda değerlerin yaklaşık \(\%95\)’i ortalamanın iki standart sapma yakınındadır, yani \(46\) ile \(94\) arasında kalır; \(1000\) not arasında bu aralığın dışında kalan birkaç uç not da bulunur; en yüksek not \(100\) sınırına dayanmıştır. \(\blacksquare\)
10.6 Örnekler
random modülünü önceki bölümlerde öğrendiğimiz döngüler ve koleksiyonlarla birleştirelim.
Örnek 10.3 (Sayısal Loto Çekilişi) \(1\)’den \(49\)’a kadar sayılar arasından \(6\) farklı sayı çekip küçükten büyüğe sıralı olarak yazınız.
Çözüm
Çekilen bir top torbaya geri konmadığı için iadesiz seçim, yani sample gerekir; choices aynı sayıyı iki kez çekebilirdi. Sayıları range(1, 50) verir; sorted sonucu sıralar.
import random
random.seed(49)
cekilis = random.sample(range(1, 50), 6)
print("Çekilen sayılar:", sorted(cekilis))Çekilen sayılar: [5, 8, 21, 23, 27, 36]
\(49\) sayıdan \(6\)’sını seçmenin \(\binom{49}{6} = 13\,983\,816\) yolu vardır; tek bir kuponun altı sayıyı da bilme olasılığı bu yüzden yaklaşık on dört milyonda birdir. \(\blacksquare\)
Örnek 10.4 (Rastgele Sayılı Tahmin Oyunu) İç İçe Döngüler, while Döngüsü ve match-case bölümündeki sayı tahmin oyununu (Örnek 8.4), tutulan sayıyı bilgisayar rastgele seçecek biçimde yazınız.
Çözüm
Tek fark, tutulan sayının random.randint(1, 100) ile seçilmesidir. Aşağıdaki çıktıyı tekrarlanabilir kılmak için bir tohum verdik; gerçek oyunda bu satır silinir, yoksa bilgisayar her oyunda aynı sayıyı tutar.
import random
random.seed(8) # gerçek oyunda bu satırı silin
tutulan = random.randint(1, 100)
sayac = 0
while True:
sayac += 1
tahmin = int(input("Tahminin (1-100): "))
if tahmin > tutulan:
print("Daha küçük.")
elif tahmin < tutulan:
print("Daha büyük.")
else:
print(f"Bildin! Sayı {tutulan} idi, {sayac} tahminde buldun.")
breakTahminin (1-100): 50
Daha küçük.
Tahminin (1-100): 25
Daha büyük.
Tahminin (1-100): 37
Daha küçük.
Tahminin (1-100): 31
Daha küçük.
Tahminin (1-100): 28
Daha büyük.
Tahminin (1-100): 30
Bildin! Sayı 30 idi, 6 tahminde buldun.
\(\blacksquare\)
Örnek 10.5 (İki Zarın Toplamının 7 Olması) İki zar atıldığında toplamın \(7\) gelme olasılığını, iki zarı \(10\,000\) kez atarak tahmin ediniz ve gerçek olasılıkla karşılaştırınız.
Çözüm
İki zarın \(6 \cdot 6 = 36\) eşit olasılıklı sonucundan toplamı \(7\) olanlar \((1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)\)’dir; gerçek olasılık \(6/36 = 1/6 \approx 0{,}1667\)’dir. Deneyde her atışta iki zar atar, toplam \(7\) ise sayacı artırırız; sonunda sayacı deneme sayısına böleriz.
import random
random.seed(1)
deneme = 10000
yedi = 0
for _ in range(deneme):
zar1 = random.randint(1, 6)
zar2 = random.randint(1, 6)
if zar1 + zar2 == 7:
yedi += 1
print("Deneysel olasılık:", yedi / deneme)
print("Gerçek olasılık :", round(1 / 6, 4))Deneysel olasılık: 0.1675
Gerçek olasılık : 0.1667
Bir olasılığı çok sayıda rastgele deneme yaparak tahmin etmeye Monte Carlo yöntemi denir; deneme sayısı arttıkça tahmin gerçek değere yaklaşır. Bu yöntemin daha ileri uygulamaları Python ile Bilimsel Hesaplama notlarındadır. \(\blacksquare\)
10.7 Alıştırmalar
Alıştırmalarda verilen tohumları kullanırsanız sonuçlarınız buradakilerle aynı çıkar.
Alıştırma 10.1 (Yazı Tura) Bir parayı \(100\) kez atıp kaç kez yazı geldiğini sayınız. Tohum olarak \(11\) kullanınız.
Çözüm
Her atışta random.choice(["yazı", "tura"]) ile iki sonuçtan birini seçeriz ve sonuçları bir listede toplarız. Yazı sayısı count ile bulunur.
import random
random.seed(11)
atislar = [random.choice(["yazı", "tura"]) for _ in range(100)]
print("Yazı:", atislar.count("yazı"))
print("Tura:", atislar.count("tura"))Yazı: 56
Tura: 44
\(100\) atışta yaklaşık \(50\) yazı beklenir; birkaç atışlık sapmalar doğaldır. \(\blacksquare\)
Alıştırma 10.2 (Altı Gelene Kadar) Bir zarı \(6\) gelene kadar atan ve kaç atış yapıldığını yazan bir program yazınız. Tohum olarak \(4\) kullanınız.
Çözüm
Kaç atış gerektiği önceden bilinmediğinden bir while döngüsü kullanırız. Döngüye girebilmek için zar’a başlangıçta \(6\)’dan farklı bir değer veririz.
import random
random.seed(4)
zar = 0
atis = 0
while zar != 6:
zar = random.randint(1, 6)
atis += 1
print(zar, end=" ")
print()
print(atis, "atışta 6 geldi.")2 3 1 6
4 atışta 6 geldi.
Her atışta \(6\) gelme olasılığı \(1/6\) olduğundan, ilk \(6\) için gereken atış sayısı ortalamada \(6\)’dır. \(\blacksquare\)
Alıştırma 10.3 (İki Takıma Ayırmak) ogrenciler = ["Ali", "Ayşe", "Can", "Deniz", "Ece", "Fatih"] listesindeki öğrencileri rastgele iki üçlü takıma ayırınız. Tohum olarak \(12\) kullanınız.
Çözüm
Listeyi shuffle ile karıştırır, sonra ilk üç elemanı bir takıma, son üç elemanı öbür takıma dilimleriz. Asıl listenin bozulmaması için önce bir kopyasını alırız.
import random
random.seed(12)
ogrenciler = ["Ali", "Ayşe", "Can", "Deniz", "Ece", "Fatih"]
karisik = ogrenciler.copy()
random.shuffle(karisik)
print("1. takım:", karisik[:3])
print("2. takım:", karisik[3:])1. takım: ['Fatih', 'Ayşe', 'Ali']
2. takım: ['Ece', 'Can', 'Deniz']
\(\blacksquare\)
Alıştırma 10.4 (Rastgele Şifre) Küçük harflerden ve rakamlardan oluşan \(8\) karakterlik rastgele bir metin üretiniz. Tohum olarak \(99\) kullanınız.
Çözüm
Kullanılabilecek karakterleri bir metinde toplarız. random.choices(karakterler, k=8) iadeli seçimle \(8\) karakter seçer; aynı karakter iki kez gelebilir. Seçilen karakterleri bir döngüyle boş bir metne ekleyerek birleştiririz.
import random
random.seed(99)
karakterler = "abcdefghijklmnopqrstuvwxyz0123456789"
secilen = random.choices(karakterler, k=8)
sifre = ""
for k in secilen:
sifre += k
print(sifre)ohgi1jny
Gerçek bir şifre için random değil, öngörülemez sayılar üreten secrets modülü kullanılmalıdır; random’ın ürettiği dizi, tohumu bilen herkes tarafından yeniden hesaplanabilir. \(\blacksquare\)
Alıştırma 10.5 (Aynı Tohum, Aynı Liste) Aşağıdaki programın son satırı ne yazar? Neden?
import random
random.seed(42)
liste1 = [random.randint(1, 100) for _ in range(10)]
random.seed(42)
liste2 = [random.randint(1, 100) for _ in range(10)]
liste3 = [random.randint(1, 100) for _ in range(10)]
print(liste1)
print(liste1 == liste2, liste1 == liste3)Çözüm
liste1 ve liste2 aynı tohumdan hemen sonra üretildiği için aynı on sayıyı içerir. liste3 ise liste2’den sonra, dizinin kaldığı yerden devam ederek üretilir; üreteç başa dönmediği için farklı sayılar içerir. Son satır True False yazar.
import random
random.seed(42)
liste1 = [random.randint(1, 100) for _ in range(10)]
random.seed(42)
liste2 = [random.randint(1, 100) for _ in range(10)]
liste3 = [random.randint(1, 100) for _ in range(10)]
print(liste1)
print(liste1 == liste2, liste1 == liste3)[82, 15, 4, 95, 36, 32, 29, 18, 95, 14]
True False
İki listenin == ile karşılaştırılması, elemanlarının sırasıyla eşit olup olmadığını sorar. \(\blacksquare\)
Alıştırma 10.6 (Beş Farklı Sayı) \(1\) ile \(20\) arasından beş farklı sayıyı iki yolla seçiniz: sample ile ve bir while döngüsünde randint ile. Tohum olarak \(6\) kullanınız.
Çözüm
sample iadesiz seçim yaptığı için doğrudan beş farklı sayı verir. İkinci yolda boş bir listeyle başlar, randint ile sayı üretir ve sayı listede yoksa ekleriz; liste beş elemana ulaşınca döngü biter.
import random
random.seed(6)
print(sorted(random.sample(range(1, 21), 5)))
secilenler = []
while len(secilenler) < 5:
sayi = random.randint(1, 20)
if sayi not in secilenler:
secilenler.append(sayi)
print(sorted(secilenler))[2, 3, 9, 16, 19]
[1, 5, 12, 16, 19]
İki satır farklı sayılar içerir, çünkü ikinci seçim üretecin kaldığı yerden devam eder. \(\blacksquare\)
Bu bölümle birlikte dersin ilk yarısındaki bütün araçları gördük: değişkenler ve tipler, işlemler, girdi ve çıktı, karar yapıları, listeler ve demetler, döngüler ve rastgele sayılar. Bir sonraki bölüm Problemler: Tipler, İşlemler ve Koşullar, bu konuları “bu kodun çıktısı nedir?” sorularıyla tekrar ediyor.