Как перемешать (сделать случайным) массив в JavaScript?
У меня есть массив, который выглядит следующим образом:
var arr1 = ["a", "b", "c", "d"];
Как я могу случайным образом перемешать его?
5 ответ(ов)
Вы можете легко перемешать массив, используя map
и sort
:
let unshuffled = ['hello', 'a', 't', 'q', 1, 2, 3, {cats: true}]
let shuffled = unshuffled
.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value)
console.log(shuffled)
- Мы помещаем каждый элемент массива в объект и присваиваем ему случайный ключ для сортировки.
- Сортируем элементы по этому случайному ключу.
- Извлекаем исходные объекты, убирая обертку.
Вы можете перемешивать полиморфные массивы, и сортировка будет такой же случайной, как и результат Math.random
, что вполне достаточно для большинства задач.
Поскольку элементы сортируются по постоянным ключам, которые не пересоздаются на каждой итерации, и каждое сравнение использует одно и то же распределение, любое несоответствие в распределении Math.random
компенсируется.
Скорость
Временная сложность — O(N log N), такая же, как и у быстрой сортировки. Пространственная сложность — O(N). Это не так эффективно, как перемешивание по алгоритму Фишера-Йейтса, но, на мой взгляд, код значительно короче и более функционален. Если у вас большой массив, вам определенно стоит использовать алгоритм Фишера-Йейтса. Если у вас небольшой массив с несколькими сотнями элементов, то этот вариант может подойти.
Предупреждение!
Использование этого алгоритма не рекомендуется, так как он неэффективен и сильно предвзят; см. комментарии. Он оставлен здесь для будущих справок, поскольку идея не так уж и редка.
[1,2,3,4,5,6].sort(() => .5 - Math.random());
Этот урок на сайте javascript.info ясно объясняет различия.
Вы можете использовать библиотеку underscore.js. Метод _.shuffle()
отлично подходит для этой задачи. Вот пример использования этого метода:
var _ = require("underscore");
var arr = [1, 2, 3, 4, 5, 6];
// Тестируем _.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();
В этом примере мы создаем массив arr
и 1000 раз перемешиваем его с помощью метода _.shuffle()
, отслеживая количество раз, когда элемент со значением 1
оказывается на каждом индексе. Результаты выводятся в объект stObj
.
Что касается перемешивания массива (shuffle array) на месте, приведенный вами код в JavaScript выполняет эту задачу весьма эффективно. Давайте разберем его подробнее и посмотрим, как его можно адаптировать и протестировать на работоспособность.
Перемешивание массива на месте
function shuffleArr(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]];
}
}
Этот алгоритм реализует метод "Фишера-Йетса" (Fisher-Yates shuffle), который гарантирует равновероятное распределение каждого элемента в массиве. Важно отметить, что данная функция изменяет исходный массив.
Чистый 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 => arr.map(() => 0)); // Счетчик для каждой позиции массива
const t0 = performance.now();
const n = 1000000;
for (var i = 0; i < n; i++) {
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}`);
}
Этот код производит миллион перемешиваний массива и ведет учет того, сколько раз каждое значение оказалось на каждой позиции.
Другие решения
Кроме того, вы также можете рассмотреть альтернативные реализации:
Рекурсивный подход
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
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
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]);
}
Каждое из этих решений имеет свои особенности и может быть использовано в зависимости от ваших требований. Убедитесь, что выбрали наиболее подходящий вариант для вашего случая!
Правка: этот ответ неверный
Смотрите комментарии и 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>Случайным образом!</h1>
<button id="trigger">Сгенерировать</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>
Генерация случайных целых чисел в JavaScript в заданном диапазоне
Как объединить два массива в JavaScript и удалить дубликаты?
Как очистить массив в JavaScript?
Как получить доступ к вложенным объектам, массивам или JSON и обработать их?
Как удалить все дубликаты из массива объектов?