如何随机排列(洗牌)JavaScript数组?

2033

我有一个数组,其形式如下:

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

我该如何对它进行随机排列 / 洗牌?


16
抛出一个东西,让你可以通过Mike Bostock制作的可视化工具来看到洗牌函数实际上是多么随机:http://bost.ocks.org/mike/shuffle/compare.html - aug
6
@Blazemonger jsPref已经无人问津了。你可以在这里发帖询问哪个是最快的? - eozzy
46
这句话的意思是将一个名为arr1的数组随机排序,代码中使用了箭头函数以及Math.random()来实现。 - yuval.bl
21
简短回答:a.sort(() => Math.random() - 0.5) 的意思是将数组 a 随机排序。 - SaboSuke
4
如果在同一规范中的上几行中看到以下内容:“如果comparefn既不是未定义的,也不是对items元素的一致比较函数,则排序顺序是实现定义的。” - yuval.bl
显示剩余6条评论
71个回答

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一次(但涉及多个复制操作)。

以下函数将不被使用,但我还是提供一下;它根据本消息中使用的一对一映射返回给定排列的索引(在枚举排列时最自然的一种);它旨在处理高达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)的阶为n的排列:

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个元素的数组,您可以安全地认为没有任何偏差。


绝对优雅! - Gershom Maes

2

通过使用shuffle-array模块,您可以随机打乱数组。以下是一个简单的代码示例。

var shuffle = require('shuffle-array'),
 //collection = [1,2,3,4,5];
collection = ["a","b","c","d","e"];
shuffle(collection);

console.log(collection);

希望这能帮到您。

2

考虑将其应用于in loco或新的不可变数组,遵循其他解决方案,这里提供了一个建议的实现:

Array.prototype.shuffle = function(local){
  var a = this;
  var newArray = typeof local === "boolean" && local ? this : [];
  for (var i = 0, newIdx, curr, next; i < a.length; i++){
    newIdx = Math.floor(Math.random()*i);
    curr = a[i];
    next = a[newIdx];
    newArray[i] = next;
    newArray[newIdx] = curr;
  }
  return newArray;
};

2
// Create a places array which holds the index for each item in the
// passed in array.
// 
// Then return a new array by randomly selecting items from the
// passed in array by referencing the places array item. Removing that
// places item each time though.
function shuffle(array) {
    let places = array.map((item, index) => index);
    return array.map((item, index, array) => {
      const random_index = Math.floor(Math.random() * places.length);
      const places_value = places[random_index];
      places.splice(random_index, 1);
      return array[places_value];
    })
}

2
var shuffledArray = function(inpArr){
    //inpArr - is input array
    var arrRand = []; //this will give shuffled array
    var arrTempInd = []; // to store shuffled indexes
    var max = inpArr.length;
    var min = 0;
    var tempInd;
    var i = 0;

    do{
        //generate random index between range
        tempInd = Math.floor(Math.random() * (max - min));
        //check if index is already available in array to avoid repetition
        if(arrTempInd.indexOf(tempInd)<0){
            //push character at random index
            arrRand[i] = inpArr[tempInd];
            //push random indexes
            arrTempInd.push(tempInd);
            i++;
        }
    }
    // check if random array length is equal to input array length
    while(arrTempInd.length < max){
        return arrRand; // this will return shuffled Array
    }
};

只需将数组传递给函数,然后返回洗牌后的数组。


2

罗纳德·费舍尔和弗兰克·耶茨洗牌算法

ES2015(ES6)发布

Array.prototype.shuffle2 = function () {
    this.forEach(
        function (v, i, a) {
            let j = Math.floor(Math.random() * (i + 1));
            [a[i], a[j]] = [a[j], a[i]];
        }
    );
    return this;
}

Jet优化的ES2015 (ES6)版本
Array.prototype.shuffle3 = function () {
    var m = this.length;
    while (m) {
        let i = Math.floor(Math.random() * m--);
        [this[m], this[i]] = [this[i], this[m]];
    }
    return this;
}

3
这与BrunoLM的答案相同。 - Oriol
可能吧,但我不这么认为:1)在我的示例中有2个版本,一个是用于全面理解的版本,另一个是优化后可立即使用的版本,其中2)不使用低级技巧,3)可以在任何ES6环境中工作。请不要把新手弄糊涂了。嗯... BrunoLM更新了答案,现在他的答案更加强大:在BrunoLM的代码中没有使用Math,而是直接使用低级数字操作,因此它要快得多。现在(更新后),它可以在任何ES6环境中正常工作,特别是对于大数组非常推荐。谢谢你,Oriol。 - SynCap

2
为了完整起见,除了Durstenfeld变体的Fisher-Yates算法之外,我还要指出Sattolo算法,它只需要进行微小的更改,就可以使每个元素都改变位置。
function sattoloCycle(arr) {
   for (let i = arr.length - 1; 0 < i; i--) {
      const j = Math.floor(Math.random() * i);
      [arr[i], arr[j]] = [arr[j], arr[i]];
   }
   return arr
}

区别在于如何计算随机索引 j,使用的是 Math.random() * iMath.random() * (i + 1)

2

编辑:不要使用这个。结果总是会使元素从开头到中间更接近。谁知道,也许这个算法有用,但不适用于完全随机排序。

随机地推送或在开头添加。

['a', 'b', 'c', 'd'].reduce((acc, el) => {
  Math.random() > 0.5 ? acc.push(el) : acc.unshift(el);
  return acc;
}, []);

2

Understandable way of shuffling elements of an array

 let arr1 = ["a", "b", "c", "d"];
 
function shuffle(array){
let currentIndex = array.length;
while(currentIndex !=0){
let randomIndex = Math.floor(Math.random()*array.length);
currentIndex -=1;
let temp = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex]=temp;  
}
return array;
}
let arr2 = shuffle(arr1);
arr2.forEach(element => console.log(element));


2

我在思考一个可以在控制台中粘贴的一行代码。所有使用.sort的技巧都会给出错误的结果,这是我的实现:

 ['Bob', 'Amy', 'Joy'].map((person) => `${Math.random().toFixed(10)}${person}`).sort().map((person) => person.substr(12));

但是不要在生产代码中使用它,因为它不够优化,只能使用字符串。


它可以与任何类型的变量一起使用:array.map(e => [Math.random(), e]).sort((a, b) => a[0] - b[0]).map(e => e[1])(但不是最佳选择)。 - Gustavo Rodrigues

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接