Как рандомизировать (перетасовать) массив JavaScript?

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

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

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

Ответ 1

Де-факто несмещенный алгоритм тасования - это переключение Fisher-Yates (aka 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];
arr = shuffle(arr);
console.log(arr);

Ответ 2

Вот 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]];
    }
}

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

Используйте библиотеку 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();

Ответ 6

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

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 отменяется.

Ответ 7

NEW!

Короче & вероятно, быстрее алгоритм Shuffle Fisher-Yates

  • он использует while ---
  • побитовое слово (цифры до десяти десятичных цифр (32 бит))
  • удалены ненужные замыкания & другие вещи

  function fy (a, b, c, d) {//массив, заполнитель, местозаполнитель, заполнитель
 с = a.length; при этом (с) Ь = Math.random() * (- с + 1) | 0, д = а [с], а [с] = а [Ь], а [Ь] = d
}
Код>

размер скрипта (с именем функции fy как): 90bytes

DEMO http://jsfiddle.net/vvpoma8w/

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

Если у вас есть вопросы, просто спросите.

ИЗМЕНИТЬ

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

ПРОИЗВОДИТЕЛЬНОСТЬ: http://jsperf.com/fyshuffle

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

ИЗМЕНИТЬ Был избыток вычислений (не нужно - c + 1) , и никто не заметил

короче (4 байта) и быстрее (проверьте это!).

  function fy (a, b, c, d) {//массив, заполнитель, местозаполнитель, заполнитель
 с = a.length; при этом (с) Ь = Math.random() * c-- | 0, д = а [с], а [с] = а [Ь], а [Ь] = д
}
Код>

Кэширование где-то еще var rnd = Math.random, а затем использование rnd() также немного увеличит производительность на больших массивах.

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

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

  function fisherYates (array) {
 var count = array.length,    случайное число,    темп;
 while (count) { randomnumber = Math.random() * count-- | 0; temp = array [count]; array [count] = array [randomnumber]; array [randomnumber] = temp
 }
}
Код>

Ответ 8

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

  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" > Генерировать </button>
< p id = "results" > 0 1 2 3 4 5 6 7 8 9 </p>

Ответ 9

Некоторые ответы могут быть сокращены с использованием синтаксиса 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, наиболее эффективна (по сравнению с другими чистыми версиями).

Перестановка массива на месте

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 Pure, рекурсивный

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

Ответ 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] });
}

Ответ 15

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

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

Ответ 16

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

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

Ответ 17

на основе Fisher-Yates Shuffle, вы можете попробовать это многоразовое компонент массива. Пример:

shuffle([1, 2, 3, 4, 5]) // => [2, 4, 1, 5, 3]

Мне также нравится эта функция Lodash, которая возвращает новый массив и оставляет исходный массив неизменным:

function shuffle(array) {
    var rand, index = -1,
        length = array.length,
        result = Array(length);
    while (++index < length) {
        rand = Math.floor(Math.random() * (index + 1));
        result[index] = result[rand];
        result[rand] = array[index];
    }
    return result;
}

(Правильное раскрытие: я нахожусь в команде CoCycles.)

Ответ 18

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

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

Ответ 19

Прежде всего, посмотрите здесь для отличного визуального сравнения различных методов сортировки в 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) времени.

Ответ 20

Fisher-Yates перетасовать в 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);
  }
}

Ответ 21

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

Ответ 22

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

 /**
 * Returns a new array whose contents are a shuffled copy of the original array.
 * @param {Array} The items to shuffle.
 * /questions/12346/how-to-randomize-shuffle-a-javascript-array/86720#86720
 * 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;
};

Ответ 23

Просто чтобы пальцем в пирог. Здесь я представляю рекурсивную реализацию Shisher Fisher Yates (я думаю). Это дает равномерную случайность.

Примечание: ~~ (оператор двойной тильды) на самом деле ведет себя как Math.floor() для положительных действительных чисел. Это короткий отрезок.

  var shuffle = a = > a.length? a.splice(~~ (Math.random() * a.length), 1).concat(перетасовка (а))
                           : a;

console.log(JSON.stringify(перетасовка ([0,1,2,3,4,5,6,7,8,9])));код>

Ответ 24

Все остальные ответы основаны на 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));

Ответ 25

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

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

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

Ответ 26

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

Ответ 27

кратчайшая функция 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;
}

Ответ 28

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

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

Ответ 29

Достаточно забавно, что не было никакого неизменяющего рекурсивного ответа:

var shuffle = arr => {
  const recur = (arr,currentIndex)=>{
    console.log("What?",JSON.stringify(arr))
    if(currentIndex===0){
      return arr;
    }
    const randomIndex = Math.floor(Math.random() * currentIndex);
    const swap = arr[currentIndex];
    arr[currentIndex] = arr[randomIndex];
    arr[randomIndex] = swap;
    return recur(
      arr,
      currentIndex - 1
    );
  }
  return recur(arr.map(x=>x),arr.length-1);
};

var arr = [1,2,3,4,5,[6]];
console.log(shuffle(arr));
console.log(arr);

Ответ 30

Рандомизировать массив, используя array.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]));

демо