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


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

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

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

30 920

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

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

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

новый!

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

  1. он использует в то время как - - -
  2. побитовое к полу (номера до 10 десятичных цифр (32bit))
  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.

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

EDIT

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

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

С помощью верхней голосовал функции.

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

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

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

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

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

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

Это, вероятно, не очень эффективно, но для небольших массивов это работает просто отлично. Вот пример, чтобы вы могли видеть, насколько он случайный (или нет), и подходит ли он для вашего usecase или нет.

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, так и в узле. Это соответствует тому, что мы ожидаем от рандомизации функция.

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

Фишер-Йейтс перемешать в 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)];
  });
}

Edit: как указывает @gregers, функция сравнения вызывается со значениями, А не индексы, именно поэтому вам нужно использовать indexOf. Обратите внимание, что это изменение делает код менее подходящим для больших массивов как indexOf работает в O (n) времени.

еще одна реализация Фишер-Йейтс, используя строгий режим:

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

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

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

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

ниже код использует хорошо известный при использовании 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 элементами; она возвращает перестановку порядка 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 элементов, вы можете смело считать, что не будет абсолютно никакой предвзятости.

современное короткое встроенное решение с использованием функций 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
    }
};

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