20

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

23

У меня есть массив, который выглядит следующим образом:

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

Как я могу случайным образом перемешать его?

5 ответ(ов)

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)
  1. Мы помещаем каждый элемент массива в объект и присваиваем ему случайный ключ для сортировки.
  2. Сортируем элементы по этому случайному ключу.
  3. Извлекаем исходные объекты, убирая обертку.

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

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

Скорость

Временная сложность — O(N log N), такая же, как и у быстрой сортировки. Пространственная сложность — O(N). Это не так эффективно, как перемешивание по алгоритму Фишера-Йейтса, но, на мой взгляд, код значительно короче и более функционален. Если у вас большой массив, вам определенно стоит использовать алгоритм Фишера-Йейтса. Если у вас небольшой массив с несколькими сотнями элементов, то этот вариант может подойти.

2

Предупреждение!

Использование этого алгоритма не рекомендуется, так как он неэффективен и сильно предвзят; см. комментарии. Он оставлен здесь для будущих справок, поскольку идея не так уж и редка.

[1,2,3,4,5,6].sort(() => .5 - Math.random());

Этот урок на сайте javascript.info ясно объясняет различия.

0

Вы можете использовать библиотеку 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.

0

Что касается перемешивания массива (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]);
}

Каждое из этих решений имеет свои особенности и может быть использовано в зависимости от ваших требований. Убедитесь, что выбрали наиболее подходящий вариант для вашего случая!

0

Правка: этот ответ неверный

Смотрите комментарии и 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>
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь