Bir JavaScript dizisini rastgele nasıl karıştırırız (karıştırır)?


1265

Ben böyle bir dizi var:

var arr1 = ["a", "b", "c", "d"];

Nasıl randomize edebilir / karıştırabilirim?



6
: Tıpkı bir shuffle fonksiyonu aslında bu Mike Bostock yapılan görselleştiricisi ile ne kadar rastgele görselleştirmek ki burada bu atma bost.ocks.org/mike/shuffle/compare.html
aug

5
@Blazemonger jsPref öldü. En hızlı olanı buraya gönderebilir misiniz?
eozzy

Tek astardan ne haber? Döndürülen dizi karıştırılır. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

İndirgeme çözeltisinin O (n ^ 2) karmaşıklığı vardır. Bir milyon öğeye sahip bir dizi üzerinde çalıştırmayı deneyin.
riv

Yanıtlar:


1541

Fiili tarafsız shuffle algoritması Fisher-Yates (Knuth) Shuffle'dır.

Bkz. Https://github.com/coolaj86/knuth-shuffle

Bir görebilirsiniz burada büyük görselleştirme (ve orijinal yayını bu bağlantılı )

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

// Used like so
var arr = [2, 11, 37, 42];
shuffle(arr);
console.log(arr);

Kullanılan algoritma hakkında daha fazla bilgi .


13
Yukarıdaki yanıtı atlamaları eleman 0, şartı olmalıdır i--değil --i. Ayrıca, deney if (i==0)...eğer bu yana gereksiz olduğunu iken döngü girilebilir asla. İle çağrı daha hızlı yapılabilir . Her iki tempi veya tempj çıkarılabilir ve değerini doğrudan atanabilir myArray [I] ya da j uygun olarak. i == 0Math.floor...| 0
RobG

23
@prometheus, pahalı donanımlara bağlı olmadıkça tüm RNG'ler yalancı rasgele.
Phil H

38
@RobG yukarıdaki uygulama işlevsel olarak doğrudur. Fisher-Yates algoritmasında, döngü dizideki ilk öğe için çalıştırılamaz. Check out wikipedia da ilk öğesini atlamak diğer uygulamalar vardır. Ayrıca , döngü için ilk öğe için çalışmamanın neden önemli olduğunu anlatan bu makaleye göz atın.
theon

34
@nikola "hiç rastgele değil" benim için biraz güçlü bir nitelik. Bir kriptograf değilseniz, muhtemelen Math.Random () 'u ilk başta kullanmıyorsanız yeterince rasgele olduğunu iddia ediyorum.
toon81

20
Ah, yoda ( 0 !== currentIndex).
ffxsam

745

Fisher-Yates'in optimize edilmiş bir sürümü olan Durstenfeld shuffle'ın JavaScript uygulaması :

/* Randomize array in-place using Durstenfeld shuffle algorithm */
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

Her orijinal dizi öğesi için rastgele bir öğe seçer ve bunu bir kart destesinden rastgele seçmek gibi bir sonraki çizimden hariç tutar.

Bu akıllı dışlama, seçilen öğeyi geçerli öğeyle değiştirir, daha sonra bir sonraki rastgele öğeyi geri kalanından alır, optimum verimlilik için geriye doğru döngü yapar, rastgele seçmenin basitleştirilmesini sağlar (her zaman 0'dan başlayabilir) ve böylece son öğeyi atlar.

Algoritma çalışma zamanı O(n). Karıştırmanın yerinde yapıldığını unutmayın , böylece orijinal diziyi değiştirmek istemiyorsanız, önce bunun bir kopyasını alın .slice(0).


EDIT: ES6 / ECMAScript 2015'e güncelleme

Yeni ES6 aynı anda iki değişken atamamıza izin veriyor. Bu, iki değişkenin değerlerini değiştirmek istediğimizde kullanışlıdır, çünkü bunu bir kod satırında yapabiliriz. İşte bu özelliği kullanarak aynı fonksiyonun daha kısa bir formu.

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

22
ps ChristopheD'nin cevabı ile aynı algoritma, ancak açıklama ve daha temiz uygulama.
Laurens Holst

12
İnsanlar algoritmaya yanlış kişiyi atfediyorlar. Fisher-Yates shuffle değil, Durstenfeld shuffle . Gerçek orijinal Fisher-Yates algoritması n ^ değil, n ^ 2 kez çalışır
Pacerier

7
return arrayJavaScript, işlev bağımsız değişkenleri olarak kullanıldığında dizileri başvuru ile ilettiği için gerekli değildir . Bunun yığın alanından tasarruf etmek olduğunu düşünüyorum, ancak ilginç bir küçük özellik. Dizide karışık çalma işlemi gerçekleştirildiğinde orijinal dizi karıştırılır.
Joel Trauger

5
Bu cevaptaki uygulama dizinin alt ucunu desteklemektedir. Zor yolu buldum . Math.random() should not be multiplied with the loop counter + 1, but with array.lengt () '. Bkz. Belirli bir aralıkta JavaScript'te rasgele tam sayılar oluşturma? çok kapsamlı bir açıklama için.
Marjan Venema

13
@MarjanVenema hala bu alanı takip ediyoruz değil misiniz, ancak bu cevap olduğunu doğru ve Önerdiğiniz değişiklik aslında önyargı tanıtır. Bu hatanın güzel bir şekilde yazılması için blog.codinghorror.com/the-danger-of-naivete adresine bakın .
kullanıcı94559

133

Uyarı!
Bu algoritmanın kullanılması önerilmez , çünkü verimsiz ve güçlü bir şekilde önyargılıdır ; Yorumlara bakınız. Gelecekte referans olması için burada bırakılıyor, çünkü fikir o kadar nadir değil.

[1,2,3,4,5,6].sort(function() {
  return .5 - Math.random();
});

13
Ben basit bir rasgele vermek için bu çözümü seviyorum
Alex K

147
Bu aşağı doğru oylama bu kadar rasgele değil. Neden bu kadar çok oyu olduğunu bilmiyorum. Bu yöntemi kullanmayın. Güzel görünüyor, ama tamamen doğru değil. İşte dizinizdeki her sayının dizine [0] kaç kez çarptığına dair 10.000 yinelemeden sonraki sonuçlar (diğer sonuçları da verebilirim): 1 = 29.19%, 2 = 29.53%, 3 = 20.06%, 4 = 11.91%, 5 =% 5.99, 6 = 3.32%
radtad

8
Nispeten küçük bir diziyi randomize etmeniz ve kriptografik şeylerle uğraşmamanız iyi olur. Tamamen katılıyorum, daha fazla rastgelelik gerekiyorsa daha karmaşık bir çözüm kullanmanız gerektiğini kabul ediyorum .
deadrunk


12
Sorun, yanlış sonuçlar verecek olan deterministik olmamasıdır (1> 2 ve 2> 3 ise, 1> 3'e verilmelidir, ancak bu bunu garanti etmeyecektir. @radtad).
MatsLindh

73

Array'den bir prototip olarak kullanılabilir veya kullanılmalıdır:

ChristopheD'den:

Array.prototype.shuffle = function() {
  var i = this.length, j, temp;
  if ( i == 0 ) return this;
  while ( --i ) {
     j = Math.floor( Math.random() * ( i + 1 ) );
     temp = this[i];
     this[i] = this[j];
     this[j] = temp;
  }
  return this;
}

42
Muhtemelen başkasının uygulanması üzerinde stomping hariç, IMOHO, bu hiçbir fayda ..
user2864740 15:14

2
Array prototipinde kullanılırsa, yalnızca shuffle dışında adlandırılmalıdır .
Kurt

57
Yerel Prototiplerin uzatılmasından kaçınılabilir (veya çekilmelidir): javascriptweblog.wordpress.com/2011/12/05/…
Wédney Yuri

12
Bunu yapmamalısınız; bundan etkilenen her bir dizi artık ... in kullanılarak güvenli bir şekilde yinelenemez. Yerel prototipleri genişletmeyin.

18
@TinyGiant Aslında: for...indiziler üzerinde yineleme yapmak için döngüler kullanmayın .
Conor O'Brien

69

Harita ve sıralama ile kolayca yapabilirsiniz:

let unshuffled = ['hello', 'a', 't', 'q', 1, 2, 3, {cats: true}]

let shuffled = unshuffled
  .map((a) => ({sort: Math.random(), value: a}))
  .sort((a, b) => a.sort - b.sort)
  .map((a) => a.value)
  1. Dizideki her öğeyi bir nesneye koyarız ve rastgele bir sıralama anahtarı veririz
  2. Rastgele anahtarı kullanarak sıralarız
  3. Orijinal nesneleri almak için eşleştiriyoruz

Polimorfik dizileri karıştırabilirsiniz ve sıralama çoğu amaç için yeterince iyi olan Math.random kadar rasgele.

Öğeler, her yinelemeyi yeniden oluşturmayan tutarlı anahtarlara göre sıralandığından ve her karşılaştırma aynı dağıtımdan çekildiğinden, Math.random dağıtımındaki rasgele olmayan öğeler iptal edilir.

hız

Zaman karmaşıklığı O (N log N), hızlı sıralama ile aynıdır. Uzay karmaşıklığı O (N) 'dir. Bu bir Fischer Yates shuffle kadar etkili değil, ama bence, kod önemli ölçüde daha kısa ve daha işlevsel. Geniş bir diziniz varsa kesinlikle Fischer Yates kullanmalısınız. Birkaç yüz öğeden oluşan küçük bir diziniz varsa, bunu yapabilirsiniz.


1
@superluminary Hata! Haklısın. Bu cevabın zaten aynı yaklaşımı kullandığına dikkat edin .
Bergi

@Bergi - Ah evet, haklısın, bence uygulamam biraz daha güzel.
superluminary

3
Çok hoş. Bu js'deki Schwartz dönüşümidir.
Mark Grimes

@torazaburo - Fischer Yates kadar performans göstermiyor, ancak daha güzel ve kod daha küçük. Kod her zaman bir değiş tokuştur. Eğer büyük bir dizi olsaydı, Knuth kullanırdım. Birkaç yüz ürünüm olsaydı, bunu yapardım.
superluminary

1
@BenCarp - Anlaşıldı, En hızlı çözüm değil ve büyük bir dizide kullanmak istemezsiniz, ancak kodda ham hızdan daha fazla husus vardır.
superluminary

64

Underscore.js kütüphanesini kullanın. Yöntem _.shuffle()bu durum için güzel. İşte yöntem ile bir örnek:

var _ = require("underscore");

var arr = [1,2,3,4,5,6];
// Testing _.shuffle
var testShuffle = function () {
  var indexOne = 0;
    var stObj = {
      '0': 0,
      '1': 1,
      '2': 2,
      '3': 3,
      '4': 4,
      '5': 5
    };
    for (var i = 0; i < 1000; i++) {
      arr = _.shuffle(arr);
      indexOne = _.indexOf(arr, 1);
      stObj[indexOne] ++;
    }
    console.log(stObj);
};
testShuffle();

12
Mükemmel cevap! Teşekkürler. İnsanları her yerde potansiyel olarak buggy işlevlerini kopyalayıp yapıştırmak yerine kütüphaneleri kullanmaya teşvik ettiği için diğer cevaplara tercih ederim.
frabcus

60
@frabcus: Sadece bir shuffleişlev elde etmek için tüm kütüphaneyi dahil etmenin bir anlamı yok .
Blender

11
@Blender'a katılmıyorum. İhtiyacınız olan bir işlevi elde etmek için kitaplığın tamamını dahil etmenin birçok nedeni vardır. Bunlardan biri, kendiniz yazdığınızda bir hata riski daha az olmasıdır. Bu bir performans sorunu ise, bunu kullanmamalısınız. Ancak bunun bir performans sorunu olabileceği anlamına gelmez.
Daniel Kaplan

7
@tieTYT: Peki neden kütüphanenin geri kalanına ihtiyacınız var? Fisher-Yates shuffle'ın uygulanması önemsizdir. Bir diziden rastgele bir öğe seçmek için bir kütüphaneye ihtiyacınız yoktur (umarım), bu yüzden aslında birden fazla işlevi kullanmayacaksanız bir kütüphane kullanmak için hiçbir neden yoktur.
Blender

18
@Blender: Bunun bir sebebini verdim. 1) Sizi temin ederim, ne kadar önemsiz olursa olsun, yazdığınız herhangi bir koda bir hata getirebilirsiniz. Neden riske atasınız? 2) Önceden optimize etmeyin. 3) Bir shuffle algo'ya ihtiyaç duyduğunuz zamanın% 99'u, uygulamanız bir shuffle algo yazmakla ilgili değildir. Bu bir shuffle algo gerektiren bir şey hakkında . Başkalarının çalışmalarından yararlanın. Zorunlu olmadıkça uygulama ayrıntılarını düşünmeyin.
Daniel Kaplan

50

YENİ!

Daha kısa ve muhtemelen * daha hızlı Fisher-Yates shuffle algoritması

  1. kullanırken kullanır ---
  2. zemine kadar bit (10 ondalık basamağa kadar olan sayılar (32 bit))
  3. gereksiz kapaklar ve diğer şeyler kaldırıldı

function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
 c=a.length;while(c)b=Math.random()*(--c+1)|0,d=a[c],a[c]=a[b],a[b]=d
}

komut dosyası boyutu (işlev adı olarak fy ile): 90bayt

DEMO http://jsfiddle.net/vvpoma8w/

* muhtemelen krom hariç tüm tarayıcılarda daha hızlı.

Herhangi bir sorunuz varsa sorun.

DÜZENLE

evet daha hızlı

PERFORMANS: http://jsperf.com/fyshuffle

en çok oy alan fonksiyonları kullanarak.

DÜZENLEME Fazla hesaplama yapıldı (gerek yok --c + 1) ve kimse fark etmedi

daha kısa (4 bayt) ve daha hızlı (test edin!).

function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
 c=a.length;while(c)b=Math.random()*c--|0,d=a[c],a[c]=a[b],a[b]=d
}

Başka bir yerde önbellekleme var rnd=Math.randomve daha sonra kullanım rnd()da büyük dizilerdeki performansı biraz artıracaktır.

http://jsfiddle.net/vvpoma8w/2/

Okunabilir sürüm (orijinal sürümü kullanın. Bu daha yavaştır, vars, "&"; kapanışları gibi işe yaramaz, kodun kendisi de daha kısadır ... belki bunu okuyun Javascript kodu nasıl 'küçültülür' , btw aşağıdaki kodu yukarıdaki gibi bir javascript küçültücüsünde sıkıştırın.)

function fisherYates( array ){
 var count = array.length,
     randomnumber,
     temp;
 while( count ){
  randomnumber = Math.random() * count-- | 0;
  temp = array[count];
  array[count] = array[randomnumber];
  array[randomnumber] = temp
 }
}

6
Daha hızlı çoğu tarayıcı performansı ... 2x göz at ... ama ihtiyaçları daha jsperf test ...
cocco

10
js birçok kısayolu ve yazmanın farklı yollarını kabul eden bir dildir .. burada çok yavaş okunabilir fonksiyonlar varken burada nasıl daha performanslı bir şekilde yapılabileceğini göstermek istiyorum, aynı zamanda bazı baytları da kaydetme ... bitsel ve steno burada gerçekten küçümseniyor ve web buggy ve yavaş kod dolu.
cocco

Bir slam dunk perf artışı değil. Değişim fyve shuffle prototypeben, olsun fyOS X 10.9.5 üzerinde Chrome 37 alt (% 81 daha yavaş ~ 20k op ~ 100k karşılaştırıldığında) ve Safari 7.1 de sürekli bunun% 8 daha yavaş ~ kalmış. YMMV, ama her zaman daha hızlı değil. jsperf.com/fyshuffle/3
Spig

istatistikleri tekrar kontrol edin ... Zaten krom yazdım çünkü matematik optimize, diğer tüm bitwise katta ve daha hızlı daha yavaş. IE, firefox ve aynı zamanda mobil cihazlar kontrol
edin.Opera

1
Bu korkunç bir cevap. SO bir karanlık rekabet değil.
Köpek yavrusu

39

Düzenleme: Bu cevap yanlış

Yorumlara ve https://stackoverflow.com/a/18650169/28234 adresine bakın . Burada referans olarak bırakılıyor çünkü fikir nadir değil.


Küçük diziler için çok basit bir yol şudur:

const someArray = [1, 2, 3, 4, 5];

someArray.sort(() => Math.random() - 0.5);

Muhtemelen çok verimli değil, ancak küçük diziler için bu iyi çalışıyor. İşte bir örnek, ne kadar rasgele (veya değil) olduğunu ve kullanıcı tabanınıza uyup uymadığını görebilirsiniz.

const resultsEl = document.querySelector('#results');
const buttonEl = document.querySelector('#trigger');

const generateArrayAndRandomize = () => {
  const someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
  someArray.sort(() => Math.random() - 0.5);
  return someArray;
};

const renderResultsToDom = (results, el) => {
  el.innerHTML = results.join(' ');
};

buttonEl.addEventListener('click', () => renderResultsToDom(generateArrayAndRandomize(), resultsEl));
<h1>Randomize!</h1>
<button id="trigger">Generate</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>


Güzel olan, ama her seferinde tam bir rastgele eleman üretir mi?
DDD

Seni doğru anladığımdan emin değilim. Bu yaklaşım, sıralama dizisini her çağırdığınızda diziyi rastgele bir şekilde karıştırır (sözde rastgele olsa da) - bariz nedenlerden dolayı kararlı bir sıralama değildir.
Kris Selbekk

4
Stackoverflow.com/a/18650169/28234 adresinde açıklananla aynı nedenlerden dolayı . Bu, erken öğeleri dizinin başlangıcına yakın bırakma olasılığı çok daha yüksektir.
AlexC

7
Bu, bir diziyi karıştırmanız gerektiğinde harika, kolay bir astardır, ancak sonuçların akademik olarak makul derecede rastgele olması konusunda çok fazla umursamayın. Bazen, mükemmellik için son birkaç santim değerinden daha fazla zaman alır.
Daniel Griscom

1
Bu işe yarasa güzel olurdu, ama işe yaramaz. Hızlı aramanın çalışma şekli nedeniyle, tutarsız bir karşılaştırıcının dizi öğelerini orijinal konumlarına yakın bırakması muhtemeldir. Diziniz şifrelenmeyecek.
superluminary

39

Güvenilir, Verimli, Kısa

Bu sayfadaki bazı çözümler güvenilir değildir (diziyi yalnızca kısmen rasgele). Diğer çözümler önemli ölçüde daha az verimlidir. İletestShuffleArrayFun biz güvenilirlik ve performans için dizi karıştırma fonksiyonları test edebilirsiniz (aşağıya bakınız). Aşağıdaki çözümler şunlardır: güvenilir, verimli ve kısa (ES6 sözdizimi kullanılarak)

[Karşılaştırma testleri testShuffleArrayFunGoogle Chrome'daki diğer çözümlerle karşılaştırıldı ]

Diziyi Karıştır

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

ES6 Pure, Yinelemeli

    const getShuffledArr = arr => {
        const newArr = arr.slice()
        for (let i = newArr.length - 1; i > 0; i--) {
            const rand = Math.floor(Math.random() * (i + 1));
            [newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
        }
        return newArr
    };

Güvenilirlik ve Performans Testi

Bu sayfada görebileceğiniz gibi, geçmişte burada yanlış çözümler sunulmuştu. Ben yazdım ve herhangi bir saf (yan etkisi yok) dizi rastgele fonksiyonlarını test etmek için aşağıdaki işlevi kullandık.

    function testShuffleArrayFun(getShuffledArrayFun){
        const arr = [0,1,2,3,4,5,6,7,8,9]

        var countArr = arr.map(el=>{
            return arr.map(
                el=> 0
            )
        }) //   For each possible position in the shuffledArr and for 
           //   each possible value, we'll create a counter. 
        const t0 = performance.now()
        const n = 1000000
        for (var i=0 ; i<n ; i++){
            //   We'll call getShuffledArrayFun n times. 
            //   And for each iteration, we'll increment the counter. 
            var shuffledArr = getShuffledArrayFun(arr)
            shuffledArr.forEach(
                (value,key)=>{countArr[key][value]++}
            )
        }
        const t1 = performance.now()
        console.log(`Count Values in position`)
        console.table(countArr)

        const frequencyArr = countArr.map( positionArr => (
            positionArr.map(  
                count => count/n
            )
        )) 

        console.log("Frequency of value in position")
        console.table(frequencyArr)
        console.log(`total time: ${t1-t0}`)
    }

Diğer Çözümler

Sadece eğlence için diğer çözümler.

ES6 Saf, Özyinelemeli

    const getShuffledArr = arr => {
        if (arr.length === 1) {return arr};
        const rand = Math.floor(Math.random() * arr.length);
        return [arr[rand], ...getShuffledArr(arr.filter((_, i) => i != rand))];
    };

Array.map kullanarak ES6 Pure

    function getShuffledArr (arr){
        return [...arr].map( (_, i, arrCopy) => {
            var rand = i + ( Math.floor( Math.random() * (arrCopy.length - i) ) );
            [arrCopy[rand], arrCopy[i]] = [arrCopy[i], arrCopy[rand]]
            return arrCopy[i]
        })
    }

Array.reduce kullanarak ES6 Pure

    function getShuffledArr (arr){
        return arr.reduce( 
            (newArr, _, i) => {
                var rand = i + ( Math.floor( Math.random() * (newArr.length - i) ) );
                [newArr[rand], newArr[i]] = [newArr[i], newArr[rand]]
                return newArr
            }, [...arr]
        )
    }

Peki, ES6 (ES2015) nerede? [array[i], array[rand]]=[array[rand], array[i]]? Belki bunun nasıl çalıştığını anlayabilirsiniz. Neden aşağı doğru yinelemeyi seçiyorsunuz?
sheriffderek

@sheriffderek Evet, kullandığım ES6 özelliği aynı anda iki değişkenin atanmasıdır, bu da iki değişkenin bir kod satırında değiştirilmesini sağlar.
Ben Carp

Yükselen Algoritmayı öneren @sheriffderek'e teşekkür ederiz. Yükselen algoritma indüksiyonda kanıtlanabilir.
Ben Carp

23

@Laurens Holsts cevabına ekleniyor. Bu% 50 sıkıştırılmıştır.

function shuffleArray(d) {
  for (var c = d.length - 1; c > 0; c--) {
    var b = Math.floor(Math.random() * (c + 1));
    var a = d[c];
    d[c] = d[b];
    d[b] = a;
  }
  return d
};

3
İnsanları, yığın taşması kodunu yapıştırmak yerine _. shuffle kullanmaya teşvik etmeliyiz; ve insanları yığın taşma cevaplarını sıkıştırmaktan caydırmalıyız. Jsmin bunun içindir.
David Jones

45
@DavidJones: Neden sadece bir diziyi karıştırmak için bir 4kb kütüphanesinin tamamını dahil edeyim?
Blender

1
@KingKongFrog isim çağrısı da makul bir topluluğun bir araya gelmesi için elverişli değildir.
buğdaylar

2
var b = b dışında döngü bildirmek ve b = bir döngü içinde atamak yerine bir döngüde yapmak verimli mi?
Alex K

2
@Brian olmaz bir fark yaratmak; kaldırma işlemi kaynak kodu ayrıştırıldığında gerçekleşir. Muhtemelen dahil değil.
user2864740

23

Düzenleme: Bu cevap yanlış

Bkz. Https://stackoverflow.com/a/18650169/28234 . Burada referans olarak bırakılıyor çünkü fikir nadir değil.

//one line solution
shuffle = (array) => array.sort(() => Math.random() - 0.5);


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

https://javascript.info/task/shuffle

Math.random() - 0.5 pozitif veya negatif olabilen rastgele bir sayıdır, bu nedenle sıralama işlevi öğeleri rastgele yeniden sıralar.


17

ES2015 ile bunu kullanabilirsiniz:

Array.prototype.shuffle = function() {
  let m = this.length, i;
  while (m) {
    i = (Math.random() * m--) >>> 0;
    [this[m], this[i]] = [this[i], this[m]]
  }
  return this;
}

Kullanımı:

[1, 2, 3, 4, 5, 6, 7].shuffle();

4
Kısaltmak için n >>> 0yerine kullanmalısınız ~~n. Dizi indeksleri 2³¹-1'den yüksek olabilir.
Oriol

1
Bu şekilde
yıkım

14

Bu varyantı, bu sorunun kopyasında "yazar tarafından silindi" yanıtlarında takıldığımı buldum. Zaten çok sayıda oyu olan diğer cevapların aksine, bu:

  1. Aslında rastgele
  2. Yerinde değil (dolayısıyla shuffledadı yerine shuffle)
  3. Burada birden fazla varyantla mevcut değil

İşte onu gösteren bir jsfiddle .

Array.prototype.shuffled = function() {
  return this.map(function(n){ return [Math.random(), n] })
             .sort().map(function(n){ return n[1] });
}

(Diziyi, özellikle daha büyük diziler için rastgele vermenin çok verimsiz bir yol olduğu için silindiğinden şüpheleniyorum ... oysa kabul edilen cevap ve bu cevabın diğer klonları yerinde rastgele gelişiyor).
WiredPrairie

1
Evet, ancak iyi bilinen yanlış cevabın hala bir sürü oyla verildiği göz önüne alındığında, en azından verimsiz ama doğru bir çözümden bahsedilmelidir.
Daniel Martin

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });- rastgele bir sıralama vermez ve kullanırsanız utanabilirsiniz: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Daniel Martin

3
.sort(function(a,b){ return a[0] - b[0]; })Sıralamanın değerleri sayısal olarak karşılaştırmasını istiyorsanız kullanmanız gerekir . Varsayılan .sort()karşılaştırıcı bunun dikkate alacaktır, yani lexicographic olduğu 10daha az olması 2beri 1az olduğunu 2.
4castle

@ 4castle Tamam, kodu güncelledim, ancak geri alacağım: sözlükbilimsel düzen ve sayısal düzen arasındaki ayrım, Math.random()üreten aralıktaki sayılar için önemli değil . (yani sözlükbilimsel düzen, 0 (dahil) ile 1 (özel) arasındaki sayılarla uğraşırken sayısal sıralama ile aynıdır)
Daniel Martin

14
var shuffle = function(array) {
   temp = [];
   originalLength = array.length;
   for (var i = 0; i < originalLength; i++) {
     temp.push(array.splice(Math.floor(Math.random()*array.length),1));
   }
   return temp;
};

Bu belli ki Fisher-Yates algoritması kadar optimal değil, ama teknik röportajlarda işe yarar mı?
davidatthepark

@Andrea Dizi uzunluğunun for döngüsü içinde değiştirilmesinden dolayı kod bozuldu. Son düzenleme ile bu düzeltildi.
Charlie Wallace

11
arr1.sort(() => Math.random() - 0.5);

1
Neden eksi 0.5? Bu sayı ne anlama geliyor?
Sartheris Stormhammer

1
@SartherisStormhammer, sıralama için bir CompareFunction kullandığımız için ve bu 0'dan büyük bir sayı döndürürse, karşılaştırılan öğeler yalnızca yönde sıralanır. Math.random () üzerinde -0.5 bize negatif bir sayı verecektir zamanın ~% 50, bu bize ters sipariş verir.
Sam Doidge

Düz ileri ve en basit çözüm. Teşekkürler
deanwilliammills

9

Aşağıdakilerle kolayca yapabilirsiniz:

// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);

Lütfen JavaScript Sıralama Dizileri'ne başvurun


Bu algoritmanın uzun zamandır kusurlu olduğu kanıtlanmıştır.

Lütfen bana kanıtla.
W3schools'a

4
Konuyu css-tricks.com/snippets/javascript/shuffle-array adresinden veya news.ycombinator.com/item?id=2728914 adresinden okuyabilirsiniz . W3schools her zaman korkunç bir bilgi kaynağı olmuştur ve olmaya devam etmektedir.

Bunun neden iyi bir yaklaşım olmadığı hakkında iyi bir tartışma için bkz. Stackoverflow.com/questions/962802/…
Charlie Wallace

8

Özyinelemeli bir çözüm:

function shuffle(a,b){
    return a.length==0?b:function(c){
        return shuffle(a,(b||[]).concat(c));
    }(a.splice(Math.floor(Math.random()*a.length),1));
};

8

Fisher-Yates javascript'te karışık. Bunu burada gönderiyorum çünkü iki yardımcı fonksiyonun (swap ve randInt) kullanımı, buradaki diğer cevaplara kıyasla algoritmayı netleştiriyor.

function swap(arr, i, j) { 
  // swaps two elements of an array in place
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
function randInt(max) { 
  // returns random integer between 0 and max-1 inclusive.
  return Math.floor(Math.random()*max);
}
function shuffle(arr) {
  // For each slot in the array (starting at the end), 
  // pick an element randomly from the unplaced elements and
  // place it in the slot, exchanging places with the 
  // element in the slot. 
  for(var slot = arr.length - 1; slot > 0; slot--){
    var element = randInt(slot+1);
    swap(arr, element, slot);
  }
}

7

Her şeyden önce, bir göz buraya javascript farklı sıralama yöntemlerinin büyük bir görsel karşılaştırma için.

İkincisi, yukarıdaki bağlantıya hızlı bir şekilde bakarsanız, random ordersıralamanın diğer yöntemlere kıyasla nispeten iyi performans gösterdiğini ve aşağıda gösterildiği gibi uygulanması son derece kolay ve hızlı olduğunu göreceksiniz :

function shuffle(array) {
  var random = array.map(Math.random);
  array.sort(function(a, b) {
    return random[array.indexOf(a)] - random[array.indexOf(b)];
  });
}

Düzenleme : @gregers tarafından işaret edildiği gibi, karşılaştırma işlevi endeksler yerine değerlerle çağrılır, bu yüzden kullanmanız gerekir indexOf. Bu değişikliğin, kodu indexOfO (n) zamanında çalıştığı gibi daha büyük diziler için daha az uygun hale getirdiğini unutmayın .


Array.prototype.sortİki geçer değerleri olarak ave b, dizine. Yani bu kod çalışmıyor.
gregers

@gregers haklısın, cevabı düzenledim. Teşekkürler.
Milo Wielondek

1
Bu çok rastgele değil. Sıralama uygulamasına bağlı olarak, en düşük dizi dizinindeki bir öğe, en yüksek dizine ulaşmak için en yüksek dizinin yanındaki öğeden daha fazla karşılaştırma gerektirebilir. Bu, en düşük dizindeki öğenin en yüksek dizine ulaşmasının daha az olası olduğu anlamına gelir.
1 'VEYA 1 -

7

kaynak diziyi değiştirmeyen bir karıştırma işlevi

Güncelleme : Burada küçük boyutlu dizilerle iyi sonuç verecek nispeten basit ( karmaşıklık perspektifinden değil ) ve kısa bir algoritma öneriyorum , ancak büyük dizilerle uğraşırken kesinlikle klasik Durstenfeld algoritmasından çok daha pahalıya mal olacak . Durstenfeld'i bu sorunun en önemli cevaplarından birinde bulabilirsiniz .

Orijinal cevap:

Eğer varsa istemeyen mutasyona sizin karıştır işlevini kaynak dizisi , daha sonra basit ile gerisini, yerel bir değişkene kopyalayabilirsiniz karıştırma mantığı .

function shuffle(array) {
  var result = [], source = array.concat([]);

  while (source.length) {
    let index = Math.floor(Math.random() * source.length);
    result.push(source[index]);
    source.splice(index, 1);
  }

  return result;
}

Karıştırma mantığı : rastgele bir dizin seçin, ardından karşılık gelen öğeyi sonuç dizisine ekleyin ve kaynak dizi kopyasından silin . Kaynak dizi boşalana kadar bu işlemi tekrarlayın .

Ve gerçekten kısa istiyorsanız, işte ne kadar uzağa gidebilirim:

function shuffle(array) {
  var result = [], source = array.concat([]);

  while (source.length) {
    let index = Math.floor(Math.random() * source.length);
    result.push(source.splice(index, 1)[0]);
  }

  return result;
}

Bu aslında orijinal Fisher-Yates algoritmasıdır ve splice"çarpıcı" dediklerini yapmanın korkunç verimsiz bir yoludur. Orijinal diziyi değiştirmek istemiyorsanız, sadece kopyalayın ve çok daha verimli Durstenfeld varyantını kullanarak bu kopyayı yerinde karıştırın.

@torazaburo, geri bildiriminiz için teşekkür ederiz. Cevabımı güncelledim, süper ölçekli bir çözümden çok güzel görünümlü bir çözüm sunduğumu açıkça belirtmek için
Evgenia Manolova

Biz de kullanabilirsiniz spliceşöyle bir kopyasını oluşturmak için yöntem: source = array.slice();.
Tayga

6

İşte KOLAY olan,

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

ayrıca örneğin, bunu kontrol edebilirsiniz burada


5

Fisher-Yates'in sıkı modunu kullanan bir başka uygulaması:

function shuffleArray(a) {
    "use strict";
    var i, t, j;
    for (i = a.length - 1; i > 0; i -= 1) {
        t = a[i];
        j = Math.floor(Math.random() * (i + 1));
        a[i] = a[j];
        a[j] = t;
    }
    return a;
}

Kesin kullanım katkısı kabul edilen cevaba ne değer vermektedir?
shortstuffsushi

Katı mod ve performansı nasıl etkilediği hakkında daha fazla bilgi edinmek için buradan okuyabilirsiniz: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Raphael C

Hmm, referans verilen belgeden belirli bir şeye işaret edebilir misiniz? Oradaki hiçbir şey, js motorunun optimize edilmesini zorlaştıracak en üstteki belirsiz bir yorumdan başka "performansı iyileştirmeye" atıfta bulunmuyor gibi görünüyor. Bu durumda, katı kullanımın neyi iyileştireceği net değil.
shortstuffsushi

Sıkı mod oldukça uzun bir süredir varlığını sürdürüyor ve herkesin her zaman kullanması gerekip gerekmediğini ve nedenini kendi fikirlerini vermesi için yeterli okumalar var. Örneğin Jslint, her zaman katı modu kullanmanız gerektiğini açıkça belirtir. Douglas Crockford, her zaman sıkı modu sadece iyi bir uygulama olarak değil, aynı zamanda V8 gibi tarayıcı js motorları tarafından nasıl farklı yorumlandığının önemli olduğuna dair oldukça fazla makale ve bazı harika videolar yazdı. Google'a şiddetle tavsiye etmenizi ve bu konuda kendi fikrinizi vermenizi öneririm.
Raphael C

İşte sıkı modda perfs hakkında eski bir iplik, biraz eski ama yine de alakalı: stackoverflow.com/questions/3145966/…
Raphael C

5

Diğer tüm cevaplar, hızlı ancak şifreli seviye randomizasyonu için uygun olmayan Math.random () 'a dayanmaktadır.

Aşağıdaki kod, kriptografik randomizasyon seviyesi için iyi bilinen Fisher-Yatesalgoritmayı kullanmaktadır .Web Cryptography API

var d = [1,2,3,4,5,6,7,8,9,10];

function shuffle(a) {
	var x, t, r = new Uint32Array(1);
	for (var i = 0, c = a.length - 1, m = a.length; i < c; i++, m--) {
		crypto.getRandomValues(r);
		x = Math.floor(r / 65536 / 65536 * m) + i;
		t = a [i], a [i] = a [x], a [x] = t;
	}

	return a;
}

console.log(shuffle(d));


4

ES6 özelliklerini kullanan modern kısa satır içi çözüm:

['a','b','c','d'].map(x => [Math.random(), x]).sort(([a], [b]) => a - b).map(([_, x]) => x);

(eğitim amaçlı)


4

Orijinal diziyi değiştirmeyen CoolAJ86 yanıtının basit bir değişikliği :

 /**
 * Returns a new array whose contents are a shuffled copy of the original array.
 * @param {Array} The items to shuffle.
 * https://stackoverflow.com/a/2450976/1673761
 * https://stackoverflow.com/a/44071316/1673761
 */
const shuffle = (array) => {
  let currentIndex = array.length;
  let temporaryValue;
  let randomIndex;
  const newArray = array.slice();
  // While there remains elements to shuffle...
  while (currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    // Swap it with the current element.
    temporaryValue = newArray[currentIndex];
    newArray[currentIndex] = newArray[randomIndex];
    newArray[randomIndex] = temporaryValue;
  }
  return newArray;
};

4

Zaten tavsiye edilen bir dizi uygulama olmasına rağmen, forEach döngüsünü kullanarak daha kısa ve daha kolay hale getirebileceğimizi hissediyorum, bu yüzden dizi uzunluğunu hesaplama konusunda endişelenmemize gerek yok ve ayrıca geçici bir değişken kullanmaktan güvenle kaçınabiliriz.

var myArr = ["a", "b", "c", "d"];

myArr.forEach((val, key) => {
  randomIndex = Math.ceil(Math.random()*(key + 1));
  myArr[key] = myArr[randomIndex];
  myArr[randomIndex] = val;
});
// see the values
console.log('Shuffled Array: ', myArr)

4

Pastada bir parmak olsun. Burada Fisher Yates shuffle özyinelemeli bir uygulama sunuyoruz (sanırım). Düzgün bir rasgelelik verir.

Not: ~~(çift tilde operatörü) aslında Math.floor()pozitif gerçek sayılar gibi davranır . Sadece kısa bir yol.

var shuffle = a => a.length ? a.splice(~~(Math.random()*a.length),1).concat(shuffle(a))
                            : a;

console.log(JSON.stringify(shuffle([0,1,2,3,4,5,6,7,8,9])));

Düzenleme: Yukarıdaki kod istihdam nedeniyle O (n ^ 2) .splice()ama takas hilesi ile O (n) splice ve shuffle ortadan kaldırabilir.

var shuffle = (a, l = a.length, r = ~~(Math.random()*l)) => l ? ([a[r],a[l-1]] = [a[l-1],a[r]], shuffle(a, l-1))
                                                              : a;

var arr = Array.from({length:3000}, (_,i) => i);
console.time("shuffle");
shuffle(arr);
console.timeEnd("shuffle");

Sorun şu ki, JS büyük özyinelemelerle işbirliği yapamıyor. Bu durumda, dizi boyutu, tarayıcı motorunuza ve bazı bilinmeyen gerçeklere bağlı olarak 3000 ~ 7000 gibi sınırlıdır.


3

Diziyi rastgele seç

 var arr = ['apple','cat','Adam','123','Zorro','petunia']; 
 var n = arr.length; var tempArr = [];

 for ( var i = 0; i < n-1; i++ ) {

    // The following line removes one random element from arr 
     // and pushes it onto tempArr 
     tempArr.push(arr.splice(Math.floor(Math.random()*arr.length),1)[0]);
 }

 // Push the remaining item onto tempArr 
 tempArr.push(arr[0]); 
 arr=tempArr; 


3

en kısa arrayShufflefonksiyon

function arrayShuffle(o) {
    for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
}

Görünüşe göre Fisher-Yates (Knuth, nötr) yerine Sattolo's yapıyorsunuz .
Arthur2e5

3

Teorik bir bakış açısından, alçakgönüllülüğe göre, bunu yapmanın en zarif yolu, 0 ile n! -1 arasında tek bir rastgele sayı elde etmek ve{0, 1, …, n!-1}(0, 1, 2, …, n-1) . Herhangi bir önemli önyargı olmadan böyle bir sayı elde etmek için yeterince güvenilir bir (sözde) rasgele jeneratör kullanabileceğiniz sürece, diğer birkaç rasgele sayıya ihtiyaç duymadan istediğinizi elde etmek için yeterli bilgiye sahipsiniz.

IEEE754 çift kesinlikli kayar sayılarla hesaplama yaparken, rastgele üretecinizin yaklaşık 15 ondalık sayı vermesini bekleyebilirsiniz. Eğer sahip olduğundan 15! = 1,307,674,368,000 (13 hane ile) kullanarak, diziler 15 öğelere içeren ve diziler 14 elemente kadar içeren anlamlı önyargı olacak varsayalım ile aşağıdaki işlevleri kullanabilirsiniz. Bu karışık çalma işleminin birçok kez hesaplanmasını gerektiren sabit boyutlu bir sorun üzerinde çalışıyorsanız, yalnızca bir kez kullandığı için diğer kodlardan daha hızlı olabilecek aşağıdaki kodu denemek isteyebilirsiniz Math.random(ancak birkaç kopyalama işlemi içerir).

Aşağıdaki işlev kullanılmayacak, ama yine de veriyorum; (0, 1, 2, …, n-1)bu mesajda kullanılan bire bir eşleşmeye göre (permütasyonları numaralandırırken en doğal olanı) verilen bir permütasyonun indeksini döndürür ; 16 öğeye kadar çalışmak üzere tasarlanmıştır:

function permIndex(p) {
    var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
    var tail = [];
    var i;
    if (p.length == 0) return 0;
    for(i=1;i<(p.length);i++) {
        if (p[i] > p[0]) tail.push(p[i]-1);
        else tail.push(p[i]);
    }
    return p[0] * fact[p.length-1] + permIndex(tail);
}

Önceki fonksiyonun (kendi sorunuz için gerekli) karşılıklılığı aşağıdadır; 16 öğeye kadar çalışmak üzere tasarlanmıştır; o emri permütasyonunu döndüren n ait (0, 1, 2, …, s-1):

function permNth(n, s) {
    var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
    var i, j;
    var p = [];
    var q = [];
    for(i=0;i<s;i++) p.push(i);
    for(i=s-1; i>=0; i--) {
        j = Math.floor(n / fact[i]);
        n -= j*fact[i];
        q.push(p[j]);
        for(;j<i;j++) p[j]=p[j+1];
    }
    return q;
}

Şimdi, sadece istediğiniz şey:

function shuffle(p) {
    var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000];
    return permNth(Math.floor(Math.random()*fact[p.length]), p.length).map(
            function(i) { return p[i]; });
}

Biraz teorik önyargı ile 16 öğeye kadar çalışmalıdır (pratik açıdan fark edilmese de); 15 element için tamamen kullanılabilir olarak görülebilir; 14'ten az eleman içeren dizilerle, kesinlikle yanlılık olmayacağını düşünebilirsiniz.


Kesinlikle zarif!
Gershom
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.