Как рандомизировать (перемешать) массив JavaScript? - PullRequest
1065 голосов
/ 16 марта 2010

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

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

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

Ответы [ 50 ]

1306 голосов
/ 16 марта 2010

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

См. 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);

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

602 голосов
/ 29 сентября 2012

Вот реализация 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;
    }
}

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

Это исключение сделано умным способом (изобретенным Дурстенфельдом для использования компьютерами) путем замены выбранного элемента текущим элементом, а затем выбора следующего случайного элемента из остатка. Для достижения оптимальной эффективности цикл выполняется в обратном направлении, так что случайный выбор упрощается (он всегда может начинаться с 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]];
    }
}
108 голосов
/ 06 сентября 2013

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

[1,2,3,4,5,6].sort(function() {
  return .5 - Math.random();
});
70 голосов
/ 13 апреля 2012

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

От 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;
}
58 голосов
/ 31 марта 2013

Используйте библиотеку 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();
49 голосов
/ 03 октября 2017

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

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 исключается.

47 голосов
/ 23 сентября 2014

NEW!

Короче и, вероятно, * быстрее алгоритм тасования Фишера-Йейтса

  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 байтов

DEMO http://jsfiddle.net/vvpoma8w/

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

Если у вас есть какие-либо вопросы, просто задавайте.

EDIT

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

ВЫПОЛНЕНИЕ: http://jsperf.com/fyshuffle

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

EDIT Был подсчет в избытке (не нужно --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/

Читаемая версия (используйте оригинальную версию. Это медленнее, переменные бесполезны, как замыкания & ";", сам код также короче ... возможно, прочитайте это Как 'minify' код Javascript , кстати, вы не можете сжимать следующий код в миниатюрах javascript, подобных приведенному выше.)

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
 }
}
34 голосов
/ 05 апреля 2017

Очень простой способ для небольших массивов заключается в следующем:

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>
22 голосов
/ 02 апреля 2013

Добавление в ответ @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
};
20 голосов
/ 11 сентября 2017

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

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

Я лично использую эту функцию, поскольку она чистая, относительно простая и, согласно моим тестам в Google Chrome, наиболее эффективна (по сравнению с другими чистыми версиями).

Массив Shuffle на месте

function shuffledArr (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]]
    }
}

Надежность и производительность

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

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

    let 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 (let i=0 ; i<n ; i++){
        //   We'll call getShuffledArrayFun n times. 
        //   And for each iteration, we'll increment the counter. 
        const 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))];
};

ES6 Pure с использованием array.map

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

ES6 Pure с использованием array.reduce

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

Typescript - тип для функции рандомизации чистого массива

Вы можете использовать любой из следующих.

type GetShuffledArr= <T>(arr:Array<T>) => Array<T> 
interface IGetShuffledArr{
    <T>(arr:Array<T>): Array<T>
}
...