Як рандомізувати (перетасувати) масив JavaScript?


1264

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

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). Спробуйте запустити його на масив з мільйонам елементів.
riv

Відповіді:


1540

Алгоритм фактичного неупередженого переміщення - перемішання Фішера-Йейта (він же Кнут).

Дивіться 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 можна видалити, а значення безпосередньо призначити myArray [i] або j, якщо це доречно. i == 0Math.floor...| 0
RobG

23
@prometheus, всі RNG є псевдовипадковими, якщо вони не підключені до дорогого обладнання.
Phil H

38
@RobG здійснення вище функціонально правильне. В алгоритмі Fisher-Yates цикл не призначений для запуску першого елемента в масиві. Перегляньте wikipedia, де є інші реалізації, які також пропускають перший елемент. Також перегляньте цю статтю, в якій розповідається про те, чому циклу важливо не запускати перший елемент.
theon

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

20
Фу, йода ( 0 !== currentIndex).
ffxsam

744

Ось реалізація JavaScript перекладу Durstenfeld , оптимізованої версії 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).


EDIT: оновлення до 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 Той самий алгоритм, що і у відповіді ChristopheD, але з поясненнями та більш чистою реалізацією.
Лорен Холст

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

133

Увага!
Використання цього алгоритму не рекомендується , оскільки він неефективний і сильно упереджений ; дивитись коментарі. Тут його залишають для подальшого ознайомлення, оскільки ідея не така вже й рідкісна.

[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

Можна було (або слід) використовувати його як прототип від масиву:

Від 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;
}

42
Дійсно ніякої користі від цього, IMOHO, за винятком, можливо, тупання над чужою реалізацією ..
user2864740

2
Якщо використовується в прототипі масиву, його слід назвати інакше, ніж просто переміщувати .
Вовк

57
Можна (або слід) уникати поширення прототипів Native: javascriptweblog.wordpress.com/2011/12/05/…
Weddury Yuri

12
Ви не повинні цього робити; кожен окремий масив, на який це впливає, вже не може бути безпечно ітералізований за допомогою ... Не поширюйте самородні прототипи.

18
@TinyGiant Насправді: не використовуйте for...inпетлі для ітерації над масивами.
Conor O'Brien

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). Це не так ефективно, як перемикання Фішера Йейтса, але, на мій погляд, код значно коротший і функціональніший. Якщо у вас є великий масив, ви, звичайно, повинні використовувати Fischer Yates. Якщо у вас є невеликий масив з кількома сотнями елементів, ви можете це зробити.


1
@superluminary На жаль, ви праві. Зауважте, що ця відповідь вже використовувала той самий підхід.
Бергі

@Bergi - Ага так, ти маєш рацію, хоча я думаю, що моя реалізація трохи гарніша.
суперлюмінація

3
Дуже хороший. Це перетворення Шварца в js.
Марк Граймс

@torazaburo - Це не так ефективно, як Fischer Yates, але воно гарніше і код менший. Кодекс - це завжди компроміс. Якби у мене був великий масив, я б використав Knuth. Якби у мене було пару сотень предметів, я би робив це.
суперлюмінація

1
@BenCarp - Погоджено, це не найшвидше рішення, і ви б не хотіли використовувати його на масивному масиві, але в коді є більше міркувань, ніж швидкість необробленого.
суперлюмінація

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

НОВИЙ!

Коротше і, ймовірно, * швидший алгоритм переміщення Fisher-Yates

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

DEMO http://jsfiddle.net/vvpoma8w/

* швидше, ймовірно, у всіх браузерах, крім хромованих.

Якщо у вас є якісь питання, просто запитайте.

EDIT

так, це швидше

ВИКОНАННЯ: http://jsperf.com/fyshuffle

використовуючи головні функції.

EDIT Був підрахунок перевищення (не потрібно --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 , btw ви не можете стиснути наступний код у minifiers 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 - це мова, яка приймає безліч ярликів і різних способів її написання .. хоча тут є багато повільно читаних функцій, я просто хочу показати, як це можна зробити більш ефективним способом, а також зберегти кілька байтів ... побіжно і скорочення тут насправді недооцінене, і в Інтернеті повно баггі та повільного коду.
кокко

Чи не збільшився парм, який зробив перф. Переміняючи fyі shuffle prototype, я fyпостійно дістаюсь внизу в Chrome 37 на OS X 10.9.5 (81% повільніше ~ 20k ops порівняно з ~ 100k) і Safari 7.1 це на ~ 8% повільніше. YMMV, але це не завжди швидше. jsperf.com/fyshuffle/3
Спіг

Перевірте статистику ще раз ... Я вже писав, що хром повільніше, оскільки вони оптимізували 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 Чистий, рекурсивний

    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 Так, функція ES6, яку я використовую, - це присвоєння двох варіантів одночасно, що дозволяє нам поміняти два вари в одному рядку коду.
Бен Карп

Подяка @sheriffderek, який запропонував алгоритм висхідного рівня. Алгоритм висхідного зростання може бути доведений в індукції.
Бен Карп

23

Додавання до @Laurens Холст відповіді. Це на 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: Навіщо я включати цілу бібліотеку 4 кбіт просто для переміщення масиву?
Блендер

1
Виклик імен @KingKongFrog також не сприяє збірці розумної спільноти.
сірі

2
чи ефективно це робити var b = в циклі, а не оголошувати b зовнішньої петлі та призначати її b = циклу?
Алекс К

2
@Brian не зміниться ; піднімання відбувається під час розбору вихідного коду. Ніхто, мабуть, не бере участь.
користувач2864740

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³ 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(); });- це не дає випадкового сортування, і якщо ви використовуєте його, ви можете збентежитися: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Даніель Мартін,

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

@ 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. При останньому редагуванні це виправляється.
Чарлі Уоллес

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

1
Чому мінус 0,5? Що означає це число?
Сартеріс Штормхаммер

1
@SartherisStormhammer тому, що для сортування ми використовуємо сравнениеFunction, і якщо це повертає число більше 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
Tính 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

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

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

Оригінальна відповідь:

Якщо ви не бажаєте, щоб ваша функція перетасування мутавала вихідний масив , ви можете скопіювати її в локальну змінну, а потім зробити все за допомогою простої логіки переміщення .

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жахливо неефективний спосіб зробити те, що вони назвали "викреслити". Якщо ви не хочете мутувати вихідний масив, просто скопіюйте його та перетасуйте цю копію на місці, використовуючи набагато ефективніший варіант Дюрстенфельда.

@torazaburo, дякую за відгук. Я оновив свою відповідь, щоб зрозуміти, що радше пропоную приємне рішення, ніж супер-масштабне
Євгенія Манолова

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

6

Ось найпростіший ,

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

для подальшого прикладу ви можете перевірити це тут


5

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

Яке значення додає вживання строго щодо прийнятої відповіді?
shortstuffsushi

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

Хм, ви могли б вказати на щось конкретне із посилається на документ? Ніщо там не посилається на "підвищення продуктивності", окрім розпливчастого коментаря вгорі про те, що потенційно утруднює оптимізацію двигуна js. У цьому випадку мені незрозуміло, яке використання строгого режиму покращило б.
shortstuffsushi

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

Ось стара тема про перси в суворому режимі, трохи стара, але все ще актуальна: 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

Просто мати палець у пирозі. Тут я представляю рекурсивну реалізацію перекладу Фішера Йейта (я думаю). Це дає рівномірну випадковість.

Примітка: ~~(оператор подвійного тильду) насправді веде себе як 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) методом swap.

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для росіян, як ви звикли, <не<=
Mohebifar


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 елементами; вона повертає перестановку порядку п з (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.