Derse geri dön

Diziyi karıştır

önem: 3

karistir(dizi) adında bir fonksiyon yazın ve bu fonksiyon dizideki elemanları her defasında rasgele yeniden dizsin.

let arr = [1, 2, 3];

shuffle(arr);
// arr = [3, 2, 1]

shuffle(arr);
// arr = [2, 1, 3]

shuffle(arr);
// arr = [3, 1, 2]
// ...

Her eleman aynı olasılıkla sıralanmalıdır. Örneğin [1,2,3] [1,2,3] veya [1,3,2] veya [3,1,2] vs. şeklinde sıralanabilir olmalıdır.

En basit çözümü aşağıdaki gibi olabilir.

function karistir(dizi) {
  dizi.sort(() => Math.random() - 0.5);
}

let dizi = [1, 2, 3];
karistir(dizi);
alert(dizi);

Yukarıdaki çalışıyor denebilir çünkü Math.random()-0.5 rasgele bir sayıdır ve pozitif veya negatif olabilir. Böylece sıralama fonksiyonu rasgele elemanları dizer.

Fakat sıralama fonksiyonu bu amaçla kullanılamaz. Tüm permütasyon aynı olasılıkta değildirler.

Aşağıdaki koda bakılacak olursa karistir 1000000 defa çalıştırılacak olursa bile olası sonuçlar şu şekildedir:

function shuffle(array) {
  array.sort(() => Math.random() - 0.5);
}

// mümkün olan tüm permütasyonların görünme sayısı.
let count = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};

for(let i = 0; i < 1000000; i++) {
  let array = [1, 2, 3];
  karistir(array);
  count[array.join('')]++;
}

// mümkün olan tüm permütasyonların görünme sayısı.
for(let key in count) {
  alert(`${key}: ${count[key]}`);
}

( Temmuz 2017 itibari ile sonuçlar aşağıdaki gibidir.)

123: 250706
132: 124425
213: 249618
231: 124880
312: 125148
321: 125223

Görüğünüz gibi 123 ve 213 ün çıkma olasılığı daha fazladır.

Sonuçlar JavaScript motoruna göre değişebilir. Fakat görüldüğü gibi bu fonksiyon çok güvenilir değildir.

Neden çalışmadı? Genel olarak konuşmak gerekirse sort kara kutudur: biz buraya bir dizi göndeririz o içinde karşılaştırma fonksiyonları vs. kullanır ve biz sıralanmış şekilde bu diziyi alırız. Bu kadar fazla rasgele karşılaştırmadan dolayı bu kara kutu deliye döndü, bu deliye dönme olayında nasıl davranacağı da JavaScript motoruna bağlıdır.

Bu problem bir kaç yöntemle çözülebilir. Fisher-Yates shuffle. Fikre göre dizi tersten başlayarak rasgele sayı ile değiştirilecek şekilde yazılmalıdır:

function karistir(array) {
  for(let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i+1)); // random index  0 ile i arasında
    [array[i], array[j]] = [array[j], array[i]]; // elemanların yer değiştirmesi için.
  }
}

Aynı şekilde test edilirse:

function karistir(array) {
  for(let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i+1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

// mümkün olan tüm permütasyonların görünme sayısı.
let count = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};

for(let i = 0; i < 1000000; i++) {
  let array = [1, 2, 3];
  karistir(array);
  count[array.join('')]++;
}

// show counts of all possible permutations
for(let key in count) {
  alert(`${key}: ${count[key]}`);
}

The example output:

123: 166693
132: 166647
213: 166628
231: 167517
312: 166199
321: 166316

Şimdi daha iyi görünüyor: tüm permütasyonlar yakın olasılıkla.

Performans yönünden Fisher-Yates algoritması harikatıdır. Hiç sıralama ile uğraşmaya gerek yok.