Как случайные (перемешать) массив JavaScript?

У меня есть такой массив:

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

Как я могу перемешивать / перемешать?

30 ответов


де-факто беспристрастным алгоритмом перемешивания является перемешивание Фишера-Йейтса (он же кнут).

см.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];
arr = shuffle(arr);
console.log(arr);

дополнительная информация об алгоритме используется.


вот реализация JavaScript Durstenfeld shuffle, компьютерно-оптимизированная версия Fisher-Yates:

/**
 * Randomize array element order 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;
    }
}

алгоритм Фишера-Йейтса работает, выбирая один случайный элемент для каждого исходного элемента массива, а затем исключая его из следующего розыгрыша. Так же, как случайный выбор из колоды карт.

это исключение сделано умным способом (изобретенным Durstenfeld для использования компьютерами) путем замены выбранного элемента на текущий элемент, а затем выбор следующего случайного элемента от остальных. Для оптимальной эффективности цикл работает назад, так что случайный выбор упрощается (он всегда может начинаться с 0), и он пропускает последний элемент, потому что больше нет других вариантов.

время работы этого алгоритма равно O (n). Обратите внимание, что перетасовка выполняется на месте. Поэтому, если вы не хотите изменять исходный массив, сначала сделайте его копию с помощью .slice(0).

обновление до ES6 / В ECMAScript 2015

новый ES6 позволяет назначать сразу две переменные. Это особенно удобно, когда мы хотим поменять значения двух переменных, так как мы можем сделать это в одной строке кода. Вот более короткая форма той же функции, используя эту функцию.

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]]; // eslint-disable-line no-param-reassign
    }
}

[community edit: этот ответ неверен; см. комментарии. Его оставляют здесь для дальнейшего использования, потому что идея не такая уж редкая.]

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

можно (или нужно) использовать его как прототип из массива:

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

использовать подчеркивание.библиотека 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();

новый!

короче и, вероятно, * быстрее Fisher-Yates shuffle алгоритм

  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 как имя функции): 90bytes

демо http://jsfiddle.net/vvpoma8w/

*быстрее, вероятно, во всех браузерах, кроме chrome.

если у вас есть какие-либо вопросы просто спросить.

редактировать

да это быстрее

производительность: http://jsperf.com/fyshuffle

используя верхние проголосованные функции.

редактировать Был расчет в избытке (не нужно --c+1)и никто не заметил

короче (4bytes) и быстрее(проверьте его!).

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

вы можете сделать это легко с помощью карты и сортировки:

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. мы unmap, чтобы получить исходные объекты

вы можете перетасовать полиморфные массивы, и сортировка так же случайна, как математика.случайный, что достаточно хорошо для большинства целей.

поскольку элементы сортируются по согласованным ключам, которые не являются восстанавливается каждая итерация, и каждое сравнение тянет из одного и того же распределения любую неслучайность в распределении математики.random отменяется.


очень простой способ для небольших массивов это:

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>

добавление к ответу @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
};

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

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

Я нашел этот вариант, висящий в ответах "удален автором" на дубликат этого вопроса. В отличие от некоторых других ответов, которые уже имеют много ответов, это:

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

рекурсивное решение:

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

С ES6, 2018

некоторые из ответов могут быть сокращены с помощью последнего ES6.

перемешать массив на месте

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

С ES6 мы можем назначить два значения сразу. Это особенно удобно в строке 4 выше, где две переменные меняются местами в одной строке кода.

оставьте исходный массив нетронутым и верните перетасованный массив

если вы хотите сделать более чистую функцию и оставить исходный массив нетронутым, вы можете просто дублируйте массив, а затем выполните тот же алгоритм.

function getShuffledArray (arr){
    let newArr = [...arr]
    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 getShuffledArrayAsc (arr){
    let newArr = [...arr];
    for (let i = 1; i < newArr.length ; i++) {
        const rand = Math.floor( Math.random() * (i + 1) ); 
        [newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
    }
    return newArr;
}

проверка надежности Рандомизирующей функции

функции выше представили равномерное распределение при передаче в' testShuffledArrayFun ' ниже, как в Chrome, так и в Node. Это в соответствии с тем, что мы ожидаем от рандомизации функция.

function testShuffledArrayFun(getShuffledArrayFun){
    // Tests the reliability of the suffleArrayFunction, by callying it 1,000 times and presenting the distribution. 
    const testArr = [0,1,2,3,4];
    const countArr = testArr.map( position => // for for each possible position in the shuffledArr, for each possible value, we'll create a counter. the counter of value 0 in position 0 will be countArr[0][0]
        testArr.map( value => 0)  
    )

    const n = 10000;
    for (var i=0 ; i<n ; i++){
        // We'll call getShuffledArrayFun for n times. For each shuffledArray we receive we'll increment the counterArr accordingly. At the end we'll print the distribution.
        var shuffledArr = getShuffledArrayFun(testArr);
        shuffledArr.forEach(
            (value, key) => {countArr[key][value]++}
        );
    }

    countArr.forEach(
        (valueCountArr,key) => {
            console.log(`Position ${key}:`);
            valueCountArr.forEach(
                (count,originalValue) => {
                    console.log(`The Value ${originalValue} appeared ${count*100/n}% `);
                }
            );
        }
    );
}

Фишер-Йейтс shuffle в javascript. Я размещаю это здесь, потому что использование двух функций утилиты (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);
  }
}

прежде всего, посмотрите здесь для отличного визуального сравнения различных методов сортировки в 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, функция сравнения вызывается со значениями, А не индексы, поэтому вам нужно использовать indexOf. Обратите внимание, что это изменение делает код менее подходящим для больших массивов как indexOf работает в O (n) времени.


еще одна реализация 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;
}

вы можете сделать это легко с:

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

пожалуйста, ссылку на Сортировка Массивов В JavaScript


функция shuffle, которая не изменяет исходный массив

обновление: здесь я предлагаю относительно простой (не сложности перспектива) и short алгоритм, который будет отлично работать с небольшими массивами, но он определенно будет стоить намного больше, чем классический 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;
}

все остальные ответы основаны на математике.случайный (), который является быстрым, но не подходит для cryptgraphic рандомизации уровень.

приведенный ниже код использует известный при использовании 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));

просто чтобы иметь палец в пироге. Здесь я представляю рекурсивную реализацию 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])));

хотя уже есть ряд реализаций, но я чувствую, что мы можем сделать его короче и проще с помощью цикла forEach, поэтому нам не нужно беспокоиться о вычислении длины массива, а также мы можем безопасно избежать использования временной переменной.

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

arr1.forEach((val, key) => {
  randomIndex = Math.ceil(Math.random()*(key + 1));
  arr1[key] = arr1[randomIndex];
  arr1[randomIndex] = val;
});

минимальные arrayShuffle функции

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

С теоретической точки зрения, наиболее элегантный способ сделать это, по моему скромному мнению, должен получить один случайное число между 0 и n!-1 и вычислить отображение один к одному из {0, 1, …, n!-1} ко всем перестановкам (0, 1, 2, …, n-1). Пока вы можете использовать (псевдо-)генератор случайных чисел, достаточно надежный для получения такого числа без какого-либо значительного смещения, у вас достаточно информации для достижения того, что вы хотите, без необходимости нескольких другие случайные числа.

при вычислении с IEEE754 двойной точностью плавающих чисел, вы можете ожидать, что ваш генератор случайных обеспечить около 15 десятичных знаков. Так как у вас есть 15!=1,307,674,368,000 (С 13 цифрами) вы можете использовать следующие функции с массивами, содержащими до 15 элементов, и предположить, что не будет значительного смещения с массивами, содержащими до 14 элементов. Если вы работаете над проблемой фиксированного размера, требующей многократного вычисления этой операции перетасовки, вы можете попробовать следующий код, который мая быть быстрее, чем другие коды, так как он использует Math.random только один раз (это однако включает в себя несколько операций копирования).

следующая функция не будет использоваться, но я все равно ее дам; она возвращает индекс заданной перестановки (0, 1, 2, …, n-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 элементами; она возвращает перестановку order n of (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 элементов, вы можете смело считать не будет абсолютно никакой предвзятости.


современное короткое встроенное решение с использованием функций ES6:

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

(для образовательных целей)


простая модификация CoolAJ86 в ответ, которые не изменяют исходный массив

 /**
 * Returns a new array whose contents are a copy shuffled of the array.
 * @param {Array} a 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 remain elements to shuffle...
  while (currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    // And swap it with the current element.
    temporaryValue = newArray[currentIndex];
    newArray[currentIndex] = newArray[randomIndex];
    newArray[randomIndex] = temporaryValue;
  }
  return newArray;
};

Array.prototype.shuffle=function(){
   var len = this.length,temp,i
   while(len){
    i=Math.random()*len-- |0;
    temp=this[len],this[len]=this[i],this[i]=temp;
   }
   return this;
}

рандомизировать массив с помощью массива.метод Splice()

function shuffleArray(array) {
   var temp = [];
   var len=array.length;
   while(len){
      temp.push(array.splice(Math.floor(Math.random()*array.length),1)[0]);
      len--;
   }
   return temp;
}
//console.log("Here >>> "+shuffleArray([4,2,3,5,8,1,0]));

демо


случайный массив

 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; 

var shuffledArray = function(inpArr){
    //inpArr - is input array
    var arrRand = []; //this will give shuffled array
    var arrTempInd = []; // to store shuffled indexes
    var max = inpArr.length;
    var min = 0;
    var tempInd;
    var i = 0;

    do{
        //generate random index between range
        tempInd = Math.floor(Math.random() * (max - min));
        //check if index is already available in array to avoid repetition
        if(arrTempInd.indexOf(tempInd)<0){
            //push character at random index
            arrRand[i] = inpArr[tempInd];
            //push random indexes
            arrTempInd.push(tempInd);
            i++;
        }
    }
    // check if random array length is equal to input array length
    while(arrTempInd.length < max){
        return arrRand; // this will return shuffled Array
    }
};

просто передайте массив в функцию и взамен получите перетасованный массив