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


1266

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

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

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



6
Просто бросать это здесь , что вы можете представить себе , как случайная функция воспроизведения в случайном порядке на самом деле с этим визуализатора Майк Босток сделал: bost.ocks.org/mike/shuffle/compare.html
авг

5
@Blazemonger jsPref мертв. Вы можете просто опубликовать здесь, который является самым быстрым?
Eozzy

Как насчет однострочника? Возвращенный массив перемешивается. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

Редукционное решение имеет сложность O (n ^ 2). Попробуйте запустить его на массиве с миллионами элементов.
РИВ

Ответы:


1543

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

Смотрите 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];
shuffle(arr);
console.log(arr);

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


13
Выше ответ скачет элемент 0, то условие должно быть i--не --i. Кроме того , тест if (i==0)...является излишним , так как если в то время как петля никогда не будет введен. Вызов может быть сделан быстрее, используя . Либо Tempi или tempj могут быть удалены , а значение непосредственно отнесены к туАггау [I] или J соответствующим образом . i == 0Math.floor...| 0
RobG

23
@prometheus, все RNG являются псевдослучайными, если они не подключены к дорогостоящему оборудованию.
Фил Х

38
@RobG реализация выше является функционально правильной. В алгоритме Фишера-Йейтса цикл не предназначен для запуска первого элемента в массиве. Проверьте википедию, где есть другие реализации, которые также пропускают первый элемент. Также ознакомьтесь с этой статьей, в которой рассказывается о том, почему важно, чтобы цикл не выполнялся для первого элемента.
Теон

34
@nikola "совсем не случайно" - это немного сильная квалификация для меня. Я бы сказал, что это достаточно случайно, если вы не криптограф, в этом случае вы, вероятно, не используете Math.Random ().
toon81

20
Тьфу, йода ( 0 !== currentIndex).
ffxsam

747

Вот JavaScript-реализация Durstenfeld shuffle , оптимизированной версии Fisher-Yates:

/* Randomize array 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]];
    }
}

22
ps Тот же алгоритм, что и в ответе Кристофа, но с объяснением и более четкой реализацией.
Лорен Хольст

12
Люди приписывают неправильный человек для алгоритма. Это не случайность Фишера-Йейтса, а случайная случайность Дюрстенфельда . Настоящий оригинальный алгоритм Фишера-Йейтса работает в n ^ 2 раза, а не в n раз
Pacerier

7
Это не требуется, return arrayпоскольку JavaScript передает массивы по ссылке при использовании в качестве аргументов функции. Я предполагаю, что это экономит место в стеке, но это небольшая интересная особенность. Выполнение перемешивания на массиве будет перемешивать исходный массив.
Джоэл Траугер

5
Реализация в этом ответе благоприятствует нижнему концу массива. Выяснил трудный путь . Math.random() should not be multiplied with the loop counter + 1, but with array.lengt () `. См. Генерация случайных целых чисел в JavaScript в определенном диапазоне? для очень полного объяснения.
Марьян Венема

13
@MarjanVenema Не уверен , что если вы все еще смотреть это пространство, но этот ответ является правильным, и изменения вы предлагаете фактически вводит смещение. См. Blog.codinghorror.com/the-danger-of-naivete для хорошего описания этой ошибки.
user94559

134

Предупреждение!
Использование этого алгоритма не рекомендуется , потому что он неэффективен и сильно смещен ; смотрите комментарии. Его оставляют здесь для дальнейшего использования, потому что идея не такая уж редкая.

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

13
Мне нравится это решение, достаточно, чтобы дать основной случайный
Алекс К

147
Понизить голосование, поскольку это не так уж случайно. Я не знаю, почему у него так много голосов. Не используйте этот метод. Это выглядит красиво, но не совсем правильно. Вот результаты после 10 000 итераций того, сколько раз каждое число в вашем массиве попадет в индекс [0] (я могу дать и другие результаты): 1 = 29,19%, 2 = 29,53%, 3 = 20,06%, 4 = 11,91%, 5 = 5,99%, 6 = 3,32%
радтад

8
Это хорошо, если вам нужно рандомизировать относительно небольшой массив и не иметь дело с криптографическими вещами. Я полностью согласен, что если вам нужно больше случайности, вам нужно использовать более сложное решение.
замер


12
Проблема в том, что он не является детерминированным, что приведет к неправильным результатам (если 1> 2 и 2> 3, следует указать, что 1> 3, но это не гарантирует этого. Это приведет к путанице в сортировке и даст результат с комментариями @radtad).
MatsLindh

73

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

От Кристофа:

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

42
На самом деле никакой пользы от этого, IMOHO, кроме, возможно, топания чьей-то реализацией ..
user2864740

2
Если используется в прототипе Array, он должен иметь другое имя, а не просто shuffle .
Вольф

57
Можно (или нужно) избегать расширения
нативных

12
Вы не должны делать это; каждый отдельный массив, затронутый этим, больше не может быть безопасно перебран с помощью for ... in. Не расширяйте родные прототипы.

18
@TinyGiant На самом деле: не используйте for...inциклы для перебора массивов.
Конор О'Брайен

69

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

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

скорость

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


1
@superluminary Ой, ты прав. Обратите внимание, что этот ответ уже использовал тот же подход.
Берги

@ Берги - Ах да, вы правы, хотя я думаю, что моя реализация немного красивее.
Сверхсветовой

3
Очень хорошо. Это преобразование Шварца в JS.
Марк Граймс

@torazaburo - он не такой производительный, как Фишер Йейтс, но он красивее и код меньше. Код всегда компромисс. Если бы у меня был большой массив, я бы использовал Кнут. Если бы у меня было пару сотен предметов, я бы сделал это.
Сверхсветовой

1
@BenCarp - Согласен, это не самое быстрое решение, и вы не захотите использовать его в массиве, но в коде больше соображений, чем сырой скорости.
superluminary

64

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

12
Отличный ответ! Спасибо. Я предпочитаю это другим ответам, поскольку это поощряет людей использовать библиотеки вместо того, чтобы копировать и вставлять потенциально ошибочные функции всюду.
frabcus

60
@frabcus: Нет смысла включать целую библиотеку только для того, чтобы получить shuffleфункцию.
Блендер

11
Я не согласен с @Blender. Есть много причин включать целую библиотеку только для того, чтобы получить нужную вам функцию. Один из них - это меньший риск ошибки, когда вы пишете это самостоятельно. Если это проблема с производительностью, то не стоит ее использовать. Но только потому, что это может быть проблемой производительности, не значит, что так будет.
Даниэль Каплан

7
@tieTYT: Так зачем тебе остальная часть библиотеки? Перестановка Фишера-Йейтса тривиальна для реализации. Вам не нужна библиотека, чтобы выбрать случайный элемент из массива (я надеюсь), поэтому нет никакой причины использовать библиотеку, если вы на самом деле не собираетесь использовать более одной функции из нее.
Блендер

18
@Blender: я дал причину почему. 1) Уверяю вас, вы можете внести ошибку в любой код, который вы пишете, независимо от того, насколько он тривиален. Зачем рисковать? 2) Не предварительно оптимизируйте. 3) В 99% случаев, когда вам нужен случайный алгоритм, ваше приложение не предназначено для написания случайного алгоритма. Это о чем-то, что нуждается в случайном алгоритме. Использовать чужую работу. Не думайте о деталях реализации, если вам не нужно.
Даниэль Каплан

50

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

ДЕМО http://jsfiddle.net/vvpoma8w/

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

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

РЕДАКТИРОВАТЬ

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

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

используя лучшие голосующие функции.

РЕДАКТИРОВАТЬ Был расчет в избытке (не нужно --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/

Читаемая версия (используйте оригинальную версию. Это медленнее, переменные бесполезны, как замыкания & ";", сам код также короче ... возможно, прочитайте это Как "минимизировать" код 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
 }
}

6
проверить производительность ... в 2 раза быстрее в большинстве браузеров ... но нужно больше тестеров jsperf ...
cocco

10
js - это язык, который принимает много сочетаний клавиш и разные способы его написания ... в то время как здесь есть много медленно читаемых функций, я просто хотел бы показать, как это можно сделать более производительным способом, также сохраняя некоторые байты ... побитово и Сокращение действительно недооценено здесь, и сеть полна глючного и медленного кода.
Cocco

Не надо увеличивать производительность. Меняя местами « fyи» shuffle prototype, я fyвхожу в нижней части Chrome 37 на OS X 10.9.5 (на 81% медленнее ~ 20 тыс. Операций по сравнению с ~ 100 тыс.) И Safari 7.1 - на ~ 8% медленнее. YMMV, но это не всегда быстрее. jsperf.com/fyshuffle/3
Spig

проверьте статистику еще раз ... я уже писал, что chrome медленнее, потому что они оптимизировали Math, на всех остальных побитовых этажах и пока быстрее. проверьте IE, Firefox, но также и мобильные устройства. Было бы также приятно увидеть оперу ...
cocco

1
Это ужасный ответ. ТАК не соревнование по омрачению.
Щенок

39

Редактировать: этот ответ неверный

Смотрите комментарии и 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>Randomize!</h1>
<button id="trigger">Generate</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>


Хорошо, но генерирует ли случайные элементы каждый раз?
DDD

Не совсем уверен, правильно ли я вас понял. Этот подход действительно будет перемешивать массив случайным образом (хотя и псевдослучайным) каждый раз, когда вы вызываете массив сортировки - по понятным причинам это не стабильная сортировка.
Крис Сельбекк

4
По тем же причинам, что и объяснены на stackoverflow.com/a/18650169/28234 . Это с большей вероятностью оставит ранние элементы в начале массива.
AlexC

7
Это отличная, простая однострочная строка, когда вам нужно зашифровать массив, но не слишком заботитесь о том, чтобы результаты были академически доказуемо случайными. Иногда эти последние несколько дюймов до совершенства занимают больше времени, чем оно того стоит.
Даниэль Гриском

1
Было бы прекрасно, если бы это сработало, но это не так. Из-за того, как работает быстрый поиск, непоследовательный компаратор может оставить элементы массива близко к их исходному положению. Ваш массив не будет зашифрован.
Сверхсветовой

39

Надежный, Эффективный, Короткий

Некоторые решения на этой странице не являются надежными (они только частично рандомизируют массив). Другие решения значительно менее эффективны. С помощью testShuffleArrayFun(см. Ниже) мы можем проверить функции перемешивания массива на надежность и производительность. Следующие решения: надежные, эффективные и короткие (с использованием синтаксиса ES6)

[Сравнительные тесты проводились с использованием testShuffleArrayFunдругих решений в Google Chrome]

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

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

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

Другие решения

Другие решения просто для удовольствия.

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

Итак, где же находится ES6 (ES2015)? [array[i], array[rand]]=[array[rand], array[i]]? Может быть, вы можете описать, как это работает. Почему вы выбираете итерацию вниз?
sheriffderek

@sheriffderek Да, функция ES6, которую я использую, - это назначение двух переменных одновременно, что позволяет нам менять две переменные в одной строке кода.
Бен Карп

Благодарю @sheriffderek, который предложил восходящий алгоритм. Восходящий алгоритм может быть доказан в индукции.
Бен Карп

23

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

3
Мы должны поощрять людей использовать _.shuffle, а не вставлять код из переполнения стека; и мы должны отговаривать людей сжимать их ответы переполнения стека. Для этого и нужен jsmin.
Дэвид Джонс

45
@DavidJones: Зачем мне включать целую библиотеку 4kb только для перемешивания массива?
Блендер

1
Обращение имени @KingKongFrog также не способствует формированию разумного сообщества.
пшеница

2
эффективно делать var b = в цикле вместо объявления b вне цикла и присвоения его b = в цикле?
Алекс К

2
@ Брайан Не имеет значения; подъем происходит, когда исходный код анализируется. Вероятно, нет.
user2864740 15.09.14

23

Редактировать: этот ответ неверный

См. Https://stackoverflow.com/a/18650169/28234 . Это оставлено здесь для справки, потому что идея не редка.

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


17

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

4
Чтобы усечь, вы должны использовать n >>> 0вместо ~~n. Индексы массива могут быть выше, чем 2³¹-1.
Ориол

1
Такое разрушение делает для такой чистой реализации +1
lukejacksonn

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

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

1
Да, но с учетом того, что общеизвестный неправильный ответ по-прежнему набирается кучей голосов, следует хотя бы упомянуть неэффективное, но правильное решение.
Даниэль Мартин

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });- это не дает случайную сортировку, и если вы используете это, вы можете в конечном итоге смущаться
Даниэль Мартин

3
Вам нужно использовать, .sort(function(a,b){ return a[0] - b[0]; })если вы хотите, чтобы сортировка сравнивала значения численно. По умолчанию .sort()компаратор лексикографический, означает , что он будет рассматривать 10быть меньше , 2так как 1меньше 2.
4касл

@ 4castle Хорошо, я обновил код, но собираюсь восстановить его: различие между лексикографическим порядком и числовым порядком не имеет значения для чисел в диапазоне, который Math.random()производит. (то есть лексикографический порядок такой же, как и числовой порядок при работе с числами от 0 (включительно) до 1 (исключительно))
Даниэль Мартин

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

Это, очевидно, не так оптимально, как алгоритм Фишера-Йейтса, но подойдет ли оно для технических интервью?
davidatthepark

@Andrea Код был нарушен из-за того, что длина цикла изменяется внутри цикла for. С последним редактированием это исправлено.
Чарли Уоллес

12
arr1.sort(() => Math.random() - 0.5);

1
Почему минус 0,5? Что означает это число?
Сартерис Штормхаммер

1
@SartherisStormhammer, потому что мы используем функцию сравнения для сортировки, и если она возвращает число больше 0, сравниваемые элементы будут упорядочены только в направлении. -0.5 в Math.random () даст нам отрицательное число ~ 50% времени, что дает нам обратный порядок.
Сэм Доидж

Прямое и простое решение. Спасибо
deanwilliammills

9

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

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

Пожалуйста, ссылки на JavaScript Сортировка массивов


Этот алгоритм давно доказал свою неисправность.

Пожалуйста, докажите мне. Я основан на w3schools
Ngô Quang

4
Вы можете прочитать ветку по адресу css-tricks.com/snippets/javascript/shuffle-array или по адресу news.ycombinator.com/item?id=2728914 . W3schools всегда был и остается ужасным источником информации.

Для хорошего обсуждения того, почему это не очень хороший подход, смотрите stackoverflow.com/questions/962802/…
Чарли Уоллес

8

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

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

8

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

7

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


Array.prototype.sortпередает в два значения как aи b, а не индекс. Так что этот код не работает.
gregers

@gregers вы правы, я отредактировал ответ. Спасибо.
Майло Виелондек

1
Это не очень случайно. В зависимости от реализации сортировки для элемента с самым низким индексом массива может потребоваться больше сравнений, чтобы получить самый высокий индекс, чем элемент рядом с самым высоким индексом. Это означает, что для элемента с самым низким индексом менее вероятно, что он достигнет самого высокого индекса.
1 'ИЛИ 1 -

7

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

Обновление : здесь я предлагаю относительно простой (не с точки зрения сложности ) и короткий алгоритм, который отлично подойдет для небольших массивов, но он определенно будет стоить намного дороже, чем классический алгоритм Дюрстенфельда , когда вы имеете дело с огромными массивами. Вы можете найти Дурстенфельд в одном из лучших ответов на этот вопрос.

Оригинальный ответ:

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

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

По сути, это оригинальный алгоритм Фишера-Йейтса, с вашим spliceужасно неэффективным способом сделать то, что они называли «вычеркиванием». Если вы не хотите изменять исходный массив, просто скопируйте его, а затем перетасуйте эту копию на место, используя гораздо более эффективный вариант Durstenfeld.

@torazaburo, спасибо за ваш отзыв. Я обновил свой ответ, чтобы прояснить, что я скорее предлагаю красивое решение, чем супермасштабное
Евгения Манолова

Мы могли бы также использовать spliceметод , чтобы создать копию следующим образом: source = array.slice();.
Тайга

6

Вот самый легкий ,

function shuffle(array) {
  return array.sort(() => Math.random() - 0.5);
}

для дальнейшего примера, вы можете проверить это здесь


5

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

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

Какое значение имеет строгий порядок добавления по сравнению с принятым ответом?
shortstuffsushi

Чтобы узнать больше о строгом режиме и о том, как он влияет на производительность, вы можете прочитать об этом здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Raphael C

Хм, не могли бы вы указать на что-то конкретное из ссылочного документа? Кажется, что ничто там не указывает на «повышение производительности», кроме смутного комментария вверху о том, что движку js сложно оптимизировать процесс оптимизации. В этом случае мне неясно, что использование строгого улучшит.
shortstuffsushi

Строгий режим существует уже довольно давно, и есть достаточно информации, чтобы каждый мог составить собственное мнение, стоит ли ему всегда его использовать или нет, и почему. Например, Jslint достаточно ясно дает понять, что вы всегда должны использовать строгий режим. Дуглас Крокфорд написал множество статей и несколько отличных видеороликов о том, почему важно всегда использовать строгий режим не только как хорошую практику, но и как по-разному его интерпретируют движки js браузера, такие как V8. Я настоятельно советую вам Google и сделать свое собственное мнение об этом.
Рафаэль С

Вот старая ветка про perfs в строгом режиме, немного старая, но все еще актуальная: stackoverflow.com/questions/3145966/…
Raphael C

5

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


4

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

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

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


4

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

 /**
 * Returns a new array whose contents are a shuffled copy of the original array.
 * @param {Array} The 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 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;
};

4

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

4

Просто чтобы иметь палец в пироге. Здесь я представляю рекурсивную реализацию 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])));

Изменить: приведенный выше код O (n ^ 2) из-за использования, .splice()но мы можем устранить сращивания и перетасовки в O (n) трюк подкачки.

var shuffle = (a, l = a.length, r = ~~(Math.random()*l)) => l ? ([a[r],a[l-1]] = [a[l-1],a[r]], shuffle(a, l-1))
                                                              : a;

var arr = Array.from({length:3000}, (_,i) => i);
console.time("shuffle");
shuffle(arr);
console.timeEnd("shuffle");

Проблема в том, что JS не может работать с большими рекурсиями. В этом конкретном случае размер вашего массива ограничен примерно 3000 ~ 7000 в зависимости от вашего браузера и некоторых неизвестных фактов.


3

Рандомизировать массив

 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; 

Там не должно быть -1для п, как вы <не использовали<=
Мохебифар


3

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


Определенно элегантно!
Гершом
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.