JavaScript配列をランダム化(シャッフル)する方法は?


1265

私はこのような配列を持っています:

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

ランダム化/シャッフルするにはどうすればよいですか?



6
:ちょうどあなたがシャッフル機能は、実際にこのマイク・ボストックが行わビジュアライザであるかランダム視覚化することができ、ここでこれを投げbost.ocks.org/mike/shuffle/compare.html
8月

5
@Blazemonger jsPrefは死んでいます。ここに投稿するだけで最速ですか?
eozzy 2016

ワンライナーはどうですか?返された配列はシャッフルされます。arr1.reduce((a、v)=> a.splice(Math.floor(Math.random()* a.length)、0、v)&& a、[])
brunettdan

reduceソリューションはO(n ^ 2)の複雑さを持っています。100万要素の配列で実行してみてください。
riv

回答:


1542

事実上の不偏シャッフルアルゴリズムは、Fisher-Yates(別名Knuth)シャッフルです。

https://github.com/coolaj86/knuth-shuffleを参照してください

あなたはここで素晴らしい視覚化を見ることができます(そしてこれにリンクされた元の投稿)

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);

使用されているアルゴリズムに関する詳細情報。


13
上記の答えは要素0をスキップします。条件はであってはなりi--ません--i。また、テストがif (i==0)...あれば以来、余分である間のループが入力されることはありません。への呼び出しは、を使用してより速く行うことができます。いずれかのテンポ又はtempjを除去することができ、値を直接に割り当てることがmyarrayの[I]またはJ適宜。i == 0Math.floor...| 0
RobG

23
@prometheus、高価なハードウェアに接続しない限り、すべてのRNGは疑似ランダムです。
Phil H

38
@RobG上記の実装は機能的に正しいです。フィッシャーイエーツアルゴリズムでは、ループは配列の最初の要素に対して実行することを意図していません。最初の要素をスキップする他の実装があるウィキペディアを確認してください。また、ループが最初の要素に対して実行されないことが重要である理由について説明しているこの記事も確認しください。
2012

34
@nikola「まったくランダムではない」は、私にとっては少し強い資格です。あなたが暗号学者でない限り、それは十分ランダムであると私は主張します。その場合、おそらく最初にMath.Random()を使用していません。
toon81 2013

20
ああ、ヨーダ(0 !== currentIndex)。
ffxsam 2017

745

次に、Fisher-Yatesの最適化されたバージョンであるDurstenfeld shuffleの JavaScript実装を示します。

/* 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;
    }
}

元の配列要素ごとにランダムな要素を選択し、カードのデッキからランダムに選択するように、次の描画からそれを除外します。

この巧妙な除外は、選択された要素を現在の要素と交換し、次に残りから次のランダム要素を選択し、最適な効率のために逆方向にループし、ランダム選択を簡略化し(常に0から開始できる)、それによって最終要素をスキップします。

アルゴリズムの実行時間はO(n)です。あなたが元の配列を変更したくない場合は、最初にそのコピーを作成してシャッフルをその場で行われていること.slice(0)


編集: ES6 / ECMAScript 2015への更新

新しいES6では、2つの変数を一度に割り当てることができます。これは、1行のコードで実行できるため、2つの変数の値を交換する場合に特に便利です。この機能を使用した同じ関数の短縮形を次に示します。

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の回答と同じアルゴリズムですが、説明と明確な実装が含まれています。
Laurens Holst 2012

12
人々はアルゴリズムのために間違った人に帰因しています。フィッシャーイェーツのシャッフルではなく、ダーステンフェルドのシャッフルです。真のオリジナルのフィッシャーイエーツアルゴリズムは、n時間ではなくn ^ 2時間で実行されます
Pacerier

7
return arrayJavaScriptは、関数の引数として使用される場合、参照によって配列を渡すため、必須ではありません。これはスタックスペースを節約するためだと思いますが、これは興味深い小さな機能です。アレイでシャッフルを実行すると、元のアレイがシャッフルされます。
Joel Trauger

5
この回答の実装では、配列の下端が優先されます。難しい方法を見つけましたMath.random() should not be multiplied with the loop counter + 1, but with array.lengt() `。JavaScriptで特定の範囲のランダムな整数生成するをご覧ください非常に包括的な説明のために。
Marjan Venema 2016

13
@MarjanVenemaまだこのスペースを監視しているかどうかはわかりませんが、この答え正しいです。提案している変更により、実際にバイアスが生じます。この間違いに関するすばらしい記事については、blog.codinghorror.com / the-danger-of -naiveteを参照してください。
user94559 2017年

133

警告!
このアルゴリズムの使用は非効率的であり、偏りが強いため、お勧めできません。コメントを参照してください。アイデアはそれほど珍しいものではないので、将来の参照用にここに残しています。

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

13
私はこのソリューションが好きで、基本的なランダムを与えるのに十分です
Alex K

147
これは実際にはランダムではないため、反対票を投じます。なぜこれほど多くの賛成票があるのか​​わかりません。この方法は使用しないでください。見た目はきれいですが、完全に正しくはありません。これは、配列内の各数値がインデックス[0]にヒットする回数を10,000回繰り返した後の結果です(他の結果も得られます):1 = 29.19%、2 = 29.53%、3 = 20.06%、4 = 11.91%、 5 = 5.99%、6 = 3.32%
ラタッド2013年

8
比較的小さな配列をランダム化し、暗号化の問題を処理しない必要がある場合は問題ありません。よりランダム性が必要な場合は、より複雑なソリューションを使用する必要があることに完全に同意します。
2013年


12
問題は、それが確定的ではなく、誤った結果をもたらすことです(1> 2および2> 3の場合、1> 3と指定する必要がありますが、これは保証されません。これにより、並べ替えが混乱し、結果がコメント化されます。 @radtadによる)。
MatsLindh 2014

73

Arrayからのプロトタイプとして使用できます(または使用する必要があります)。

ChristopheDから:

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
IMOHOは、他の誰かの実装に踏み込む可能性があることを除いて、本当にこれに対する利点はありません..
user2864740

2
Arrayプロトタイプで使用する場合は、単にshuffle以外の名前を付ける必要があります。
ウルフ

57
ネイティブプロトタイプの拡張を回避することができます(または回避する必要があります):javascriptweblog.wordpress.com/2011/12/05/…–WédneyYuri
2015

12
これを行うべきではありません。これによって影響を受けるすべてのアレイは、for ... inを使用して安全に反復することはできません。ネイティブプロトタイプを拡張しないでください。

18
@TinyGiant実際には、for...inループを使用して配列を反復しないでください。
Conor O'Brien、

69

あなたはマップとソートでそれを簡単に行うことができます:

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. 配列の各要素をオブジェクトに入れ、ランダムなソートキーを与えます
  2. ランダムキーを使用して並べ替えます
  3. 元のオブジェクトを取得するためにマップを解除します

ポリモーフィック配列をシャッフルでき、ソートはMath.randomと同じくらいランダムで、ほとんどの目的に十分です。

要素は、反復ごとに再生成されない一貫したキーに対してソートされ、各比較は同じ分布からプルするため、Math.randomの分布の非ランダム性はキャンセルされます。

速度

時間の複雑さはO(N log N)で、クイックソートと同じです。スペースの複雑さはO(N)です。これはフィッシャーイェーツのシャッフルほど効率的ではありませんが、私の意見では、コードは大幅に短く、機能的です。配列が大きい場合は、必ずフィッシャーイェーツを使用してください。数百項目の小さな配列がある場合は、これを行うことができます。


1
@superluminaryおっと、あなたは正しい。この回答はすでに同じアプローチを使用していることに注意してください。
Bergi

@Bergi-ああ、そうです、私の実装は少しきれいだと思いますが。
超夜明け2017

3
非常に素晴らしい。これは、js のシュワルツ変換です。
Mark Grimes

@torazaburo-フィッシャーイェーツほどパフォーマンスは良くありませんが、見栄えがよく、コードは小さくなります。コードは常にトレードオフです。配列が大きい場合は、Knuthを使用します。私が数百のアイテムを持っているなら、私はこれをするでしょう。

1
@BenCarp-同意します。これは最速のソリューションではないため、大規模な配列で使用したくないでしょうが、コードには、本来の速度よりも多くの考慮事項があります。

64

underscore.jsライブラリを使用します。この方法_.shuffle()はこの場合に適しています。メソッドの例を次に示します。

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
正解です。ありがとう。バグのある可能性のある関数をどこにでもコピーして貼り付けるのではなく、ライブラリを使用することを人々に奨励するため、私は他の答えよりもそれを好みます。
frabcus

60
@frabcus:shuffle関数を取得するためだけにライブラリ全体を含める意味はありません 。
Blender 2013年

11
@Blenderに同意しません。必要な関数を取得するためだけにライブラリ全体を含める理由はたくさんあります。それらの1つは、自分で書いたときにバグのリスクが少ないことです。 場合は、それがパフォーマンスの問題です、あなたはそれを使用するべきではありません。しかし、それパフォーマンスの問題である可能性があるからといって、それそうなるとは限りません。
Daniel Kaplan 2013

7
@tieTYT:では、なぜライブラリの残りの部分が必要なのですか?フィッシャーイェーツのシャッフルは、実装するのは簡単です。配列からランダムな要素を選択するためにライブラリは必要ありません(私はそう思います)。したがって、実際にライブラリから複数の関数を使用するのでない限り、ライブラリを使用する理由はありません。
Blender 2013

18
@ブレンダー:理由を説明しました。1)確かに、どんなに些細なことでも、あなたが書いたコードにバグを導入することができます。なぜそれを危険にさらすのですか?2)事前に最適化しないでください。3)シャッフルアルゴが必要な場合の99%の時間、アプリはシャッフルアルゴを作成するためのものではありません。シャッフルアルゴが必要なものです。他人の仕事を活用する。必要がない限り、実装の詳細については考えないでください。
Daniel Kaplan 2013

50

新着!

より短く、おそらく*より速いフィッシャーイェイツのシャッフルアルゴリズム

  1. それは使用しています---
  2. ビットごとにフロア(10桁までの数値(32ビット))
  3. 不要な閉鎖やその他のものを削除

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
}

スクリプトサイズ(関数名としてfyを使用):90バイト

デモ http://jsfiddle.net/vvpoma8w/

*おそらくクロームを除くすべてのブラウザでより高速です。

ご不明な点がございましたらお尋ねください。

編集する

はい、それはより速いです

パフォーマンス: http : //jsperf.com/fyshuffle

上位投票関数を使用しています。

編集 過剰な計算があり(--c + 1は必要ありません)誰も気づきませんでし

短い(4バイト)&速い(それをテストしてください!)。

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
}

別の場所var rnd=Math.randomでキャッシュしてから使用rnd()すると、大きなアレイのパフォーマンスがわずかに向上します。

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

読み取り可能なバージョンは、(元のバージョンを使用し、これは遅い、VARSは閉鎖&のような、役に立たない。「;」、コード自体も短いです...多分これを読んでどのように「縮小化のJavaScriptコード、ところで、あなたはにできません上記のようなjavascript minifiersで次のコードを圧縮します。)

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
パフォーマンスを確認してください...ほとんどのブラウザで2倍高速です...しかし、より多くのjsperfテスターが必要です...
cocco

10
jsは多くのショートカットとそれを書くためのさまざまな方法を受け入れる言語です..ここには多くの遅い読み取り可能な関数がありますが、よりパフォーマンスの高い方法でそれがどのように行われるかを示し、いくつかのバイトを節約しています...ここでは省略表現は実際には過小評価されており、Webにはバグが多く遅いコードがたくさんあります。
cocco 2014

スラムダンクのパフォーマンス向上ではありません。とを入れ替えるfyshuffle prototypefyOS X 10.9.5(〜100kと比較して81%遅い〜20k ops)のChrome 37とSafari 7.1は常に最大〜8%遅くなります。YMMVですが、常に高速であるとは限りません。 jsperf.com/fyshuffle/3
2014年

統計をもう一度確認してください...他のすべてのビット単位の床では、Mathが最適化されているため、Chromeの方が低速です。IE、Firefoxだけでなく、モバイルデバイスも確認してください。オペラも見れると思います...
cocco

1
これはひどい答えです。SOは隠蔽競争ではありません。
パピー

39

編集:この答えは正しくありません

コメントとhttps://stackoverflow.com/a/18650169/28234を参照してください。アイデアは珍しくないので、参照用にここに残しています。


小さな配列のための非常に簡単な方法はこれです:

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

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

おそらくあまり効率的ではありませんが、小さな配列の場合はこれで十分です。ここに例を示しますので、どれほどランダムであるか(またはランダムでないか)、およびユースケースに適合するかどうかを確認できます。

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>


いいものですが、毎回完全なランダム要素を生成しますか?
DDD 2017

私があなたを正しく理解したかどうかはよくわかりません。このアプローチは確かに、ソート配列を呼び出すたびにランダムな方法で(疑似ランダムではありますが)配列をシャッフルします-明らかな理由により、これは安定したソートではありません。
クリス・セルベック2017

4
stackoverflow.com/a/18650169/28234で説明されているのと同じ理由で。これは、配列の先頭近くに初期の要素を残す可能性がはるかに高くなります。
AlexC 2017年

7
これは、配列をスクランブルする必要がある場合に最適な簡単な1行ですが、結果が学術的に証明できるほどランダムであることをあまり気にしないでください。場合によっては、最後の数インチを完璧にするために、それが価値があるよりも時間がかかることがあります。
Daniel Griscom

1
これがうまくいけば素敵ですが、うまくいきません。クイック検索のしくみにより、コンパレータの一貫性が失われると、配列要素が元の位置に近くなる可能性があります。アレイはスクランブルされません。
スーパールミナリー2018年

39

信頼性が高く、効率的で、短い

このページのいくつかのソリューションは信頼できません(アレイを部分的にランダム化するだけです)。その他のソリューションは、効率が大幅に低下します。ではtestShuffleArrayFun、我々は信頼性とパフォーマンスのために、アレイシャッフル機能をテストすることができます(下記参照)。次のソリューションは、信頼性が高く、効率的で短い(ES6構文を使用)

[ testShuffleArrayFunGoogle Chromeで他のソリューションに対して比較テストが行​​われました]

配列をシャッフルする

    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純粋、反復

    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
    };

信頼性とパフォーマンスのテスト

このページでわかるように、過去にここで提供された誤ったソリューションがありました。次の関数を記述して、純粋な(副作用のない)配列ランダム化関数をテストしました。

    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}`)
    }

その他のソリューション

楽しみのための他のソリューション。

ES6純粋、再帰

    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を使用した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を使用した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]
        )
    }

それでは、ES6(ES2015)はどこにありますか?[array[i], array[rand]]=[array[rand], array[i]]?多分あなたはそれがどのように機能するかを概説できます。なぜ下向き反復を選択するのですか?
sheriffderek 2017

@sheriffderekはい、私が使用しているES6の機能は、2つの変数を一度に割り当てることです。これにより、1行のコードで2つの変数を交換できます。
ベン・カープ

昇順アルゴリズムを提案した@sheriffderekの功績です。昇順アルゴリズムは帰納的に証明することができます。
ベン・カープ

23

@Laurens Holstsの回答に追加します。これは50%圧縮されています。

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
スタックオーバーフローからコードを貼り付けるのではなく、_。shuffleを使用するように人々を奨励する必要があります。そして、スタックオーバーフローの回答を圧縮することを人々に思いとどまらせるべきです。それがjsminの目的です。
David Jones

45
@DavidJones:配列をシャッフルするためだけに4kbライブラリ全体を含めるのはなぜですか?
Blender 2013年

1
@KingKongFrogの名前の呼び出しも、合理的なコミュニティの集まりに伝導的ではありません。
ウィーティー2013年

2
var b = ループ外でbを宣言してループ内で割り当てる代わりに、ループ内で行うのが効率的b = ですか?
Alex K

2
@ブライアンは違いを生みません。巻き上げは、ソースコードが解析されるときに発生します。おそらく関与していません。
user2864740 2014

23

編集:この答えは正しくありません

https://stackoverflow.com/a/18650169/28234を参照してください。アイデアは珍しくないので、参照用にここに残しています。

//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 は正または負の乱数であるため、並べ替え関数は要素をランダムに並べ替えます。


17

ES2015では、これを使用できます。

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;
}

使用法:

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

4
切り捨てるには、のn >>> 0代わりにを使用する必要があります~~n。配列のインデックスは2³¹-1よりも大きくすることができます。
Oriol

1
このような構造破壊により、このようなクリーンな実装が可能になります+1
lukejacksonn

14

この変種は、この質問の複製に対する「著者によって削除されました」の回答でハングしていることがわかりました。すでに多くの賛成票がある他の回答とは異なり、これは次のとおりです。

  1. 実際にランダム
  2. インプレースではない(したがって、shuffledではなく名前shuffle
  3. ここにはまだ複数のバリアントが存在しません

これが使用中のjsfiddleです。

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

(特に、より大きな配列の場合、配列をランダム化するための非常に非効率的な方法であるので削除されたと思います...受け入れられた回答とその回答の他の多くのクローンがインプレースでランダム化されます)。
WiredPrairie、2015

1
ええ、でも、よく知られている間違った答えがまだたくさんの投票で上がっていることを考えると、非効率的ですが正しい解決策は少なくとも言及されるべきです。
Daniel Martin

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });-ランダムな並べ替えは行われません。使用すると、困惑する
Daniel Martin

3
.sort(function(a,b){ return a[0] - b[0]; })並べ替えで数値を比較する場合に使用する必要があります。デフォルトの.sort()コンパレータは辞書式です。つまり、はより10小さいと見なされます。212
4castle

@ 4castleわかりました。コードを更新しましたが、元に戻します。辞書式順序と数値順の区別は、Math.random()生成される範囲の数値では問題になりません。(1(排他的)に0(包括的)から数字を扱うときであること、辞書式順序は数値順と同じである)
ダニエル・マーティン

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;
};

これは明らかに、Fisher-Yatesアルゴリズムほど最適ではありませんが、技術面接では機能しますか?
davidatthepark 2016年

@Andrea forループ内で配列の長さが変更されるため、コードが壊れました。最後の編集でこれは修正されました。
チャーリーウォレス

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

1
なぜマイナス0.5?その数字はどういう意味ですか?
Sartheris Stormhammer

1
@SartherisStormhammerは、ソートにcompareFunctionを使用しているためであり、それが0より大きい数を返す場合、比較される要素は方向のみに順序付けられます。Math.random()で-0.5を指定すると、時間の約50%が負の数になり、逆の順序になります。
Sam Doidge

簡単でシンプルなソリューション。ありがとう
deanwilliammills

9

あなたはそれを簡単に行うことができます:

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

JavaScript Sorting Arraysで参照してください


このアルゴリズムには欠陥があることが長い間証明されています。

私に証明してください。W3Schoolsのに基づいてI
TínhNGOクアン

4
あなたは、スレッドを読むことができるcss-tricks.com/snippets/javascript/shuffle-array、またはでnews.ycombinator.com/item?id=2728914。W3schoolsは、これまでも、そして現在も、恐ろしい情報源です。

これが良いアプローチではない理由についての良い議論については、stackoverflow.com
Charlie Wallace

8

再帰的な解決策:

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

フィッシャーイェーツがJavaScriptでシャッフルします。2つのユーティリティ関数(swapとrandInt)を使用すると、ここで他の回答と比較してアルゴリズムが明確になるため、これをここに投稿します。

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

まず、JavaScriptでのさまざまな並べ替え方法の視覚的な比較について、こちらご覧ください

次に、上記のリンクを簡単に確認random orderすると、他のメソッドに比べてソートが比較的うまく機能しているように見えますが、以下に示すように、実装は非常に簡単で高速です。

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

編集:@gregersで指摘されているように、compare関数はインデックスではなく値で呼び出されるため、を使用する必要がありますindexOf。この変更により、indexOfO(n)時間で実行されるため、コードがより大きな配列に適さなくなります。


Array.prototype.sortインデックスではなく、およびとして2つのを渡します。したがって、このコードは機能しません。ab
2016年

@gregersあなたが正しい、私は答えを編集しました。ありがとう。
Milo Wielondek、2016年

1
これはあまりランダムではありません。ソートの実装によっては、最小の配列インデックスの要素は、最大のインデックスの隣の要素よりも最大のインデックスに到達するために、より多くの比較を必要とする場合があります。つまり、最低のインデックスにある要素が最高のインデックスに到達する可能性が低くなります。
1 'OR 1-

7

ソース配列を変更しないシャッフル関数

更新:ここでは、比較的単純な複雑さの観点からではなく)短いアルゴリズムを提案します。このアルゴリズムは、小さなサイズの配列で問題なく機能しますが、巨大な配列を処理する場合、従来のDurstenfeldアルゴリズムよりもはるかにコストがかかります。あなたは見つけることができますDurstenfeldをこの質問への回答トップの一つに。

元の答え:

シャッフル関数でソース配列を変更したくない場合は、それをローカル変数にコピーしてから、残りを単純なシャッフルロジックで実行できます。

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;
}

シャッフルロジック:ランダムなインデックスを取得し、対応する要素を結果の配列に追加して、ソース配列のコピーから削除します。ソースアレイが空になるまで、このアクションを繰り返します

そして、もし本当にあなたがそれを短くしたいなら、ここに私がどこまで得ることができるかがあります:

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;
}

これは本質的にはオリジナルのフィッシャーイェーツアルゴリズムであり、あなたspliceが彼らが「ストライクアウト」と呼んだことを実行するための恐ろしく非効率的な方法です。元の配列を変更したくない場合は、それをコピーしてから、より効率的なDurstenfeldバリアントを使用して、そのコピーを所定の場所に入れ替えます。

@torazaburo、フィードバックありがとうございます。私はそれが私はむしろ超スケーリングものよりも、見栄えの良いソリューションを提供していますことを明確にし、私の答えを更新しました
Evgenia Manolova

spliceメソッドを使用して、次のようなコピーを作成することもできますsource = array.slice();
タイガ


5

厳格モードを使用した、Fisher-Yatesのさらに別の実装:

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;
}

使用の厳格化は、受け入れられた回答に対してどのような価値を提供しますか?
shortstuffsushi 2017

ストリクトモードの詳細とパフォーマンスへの影響については、developer.mozilla.org
en

ええと、あなたは参照されたドキュメントから何か特定のものを指すことができますか?jsエンジンの最適化を潜在的に困難にする可能性があることについてのあいまいなコメントを除いて、そこには「パフォーマンスの向上」について言及しているようには見えません。この場合、どのようにstrictを使用すれば改善できるかは不明です。
shortstuffsushi '19

Strictモードはかなり前から存在しており、常に使用する必要があるかどうか、およびその理由について、だれでも自分の意見を述べるのに十分な読み取りがあります。たとえばJslintは、常に厳密モードを使用する必要があることを十分に明確にしています。Douglas Crockfordは、厳密モードを常に使用することが重要である理由だけでなく、V8などのブラウザーのjsエンジンによってどのように解釈が異なるかについて、かなりの数の記事といくつかの素晴らしいビデオを書いています。私はあなたにそれをグーグルしてあなた自身の意見を述べることを強く勧めます。
Raphael C

ここではstrictモードでパーフについての古いスレッドが、少し古いが、まだ関連している:stackoverflow.com/questions/3145966/...
ラファエルC

5

他のすべての答えは、高速ですが暗号レベルのランダム化には適さないMath.random()に基づいています。

以下のコードは、暗号化レベルのランダム化にFisher-Yates利用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機能を使用した最新の短いインラインソリューション:

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

(教育目的のため)


4

元の配列を変更しないCoolAJ86の回答の簡単な変更:

 /**
 * 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

すでに推奨されている実装は多数ありますが、forEachループを使用して短く簡単にできるので、配列の長さの計算について心配する必要がなく、一時変数の使用を安全に回避できます。

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

パイに指を入れるだけ。ここでは、Fisher Yates shuffleの再帰的な実装を紹介します(私はそう思います)。ランダム性が均一になります。

注:(~~二重チルド演算子)は、実際にはMath.floor()正の実数のように動作します。ちょうどそれがショートカットです。

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])));

編集:上記のコードはO(n ^ 2)を採用しているため.splice()、O(n)でスプライスとシャッフルを排除できます。

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");

問題は、JSが大きな再帰に対処できないことです。この特定のケースでは、配列サイズは、ブラウザーエンジンといくつかの未知の事実に応じて、3000〜7000程度に制限されます。


3

配列をランダム化

 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; 

-1あなたが使って<いなかったのでfor n があってはいけません<=
モヘビファー


3

理論的な観点から、私の考えでは、最もエレガントな方法は、0n!-1の間の単一の乱数を取得し、からすべての順列への1対1のマッピングを計算することです。{0, 1, …, n!-1}(0, 1, 2, …, n-1)。(疑似)乱数発生器を使用して、大きなバイアスなしにそのような数値を取得するのに十分な信頼性がある限り、他のいくつかの乱数を必要とせずに目的を達成するための十分な情報があります。

IEEE754倍精度浮動小数点数で計算する場合、ランダムジェネレーターが約15の10進数を提供することを期待できます。あなたが持っているので= 1,307,674,368,000 15!(13桁)を、あなたは、15個の要素までを含む配列で、以下の機能を使用し、14個の要素までを含むアレイと有意な偏りが生じないと仮定することができます。このシャッフル操作を何度も計算する必要がある固定サイズの問題に取り組む場合、1回しか使用しないため、他のコードよりも高速である可能性がある次のコードを試してくださいMath.random(ただし、複数のコピー操作が含まれます)。

次の関数は使用しませんが、とにかく与えます。(0, 1, 2, …, n-1)このメッセージで使用されている1対1のマッピング(順列を列挙するときに最も自然なもの)に従って、指定された順列のインデックスを返します。最大16個の要素を処理することを目的としています。

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);
}

前の関数の逆数(自分の質問に必要)は以下のとおりです。最大16個の要素を処理することを目的としています。次の順序nの順列を返します(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;
}

さて、あなたが望むのは単に:

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]; });
}

理論上のバイアスがわずかにある場合、最大16個の要素で機能します(ただし、実用的な観点からは目立ちません)。15要素で完全に使用可能と見なすことができます。要素数が14未満の配列の場合は、バイアスがないと考えることができます。


間違いなくエレガント!
Gershom 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.