如何随机化(shuffle)一个JavaScript数组?

Cli*_*ote 1138 javascript arrays shuffle

我有这样一个数组:

var arr1 = ["a", "b", "c", "d"];
Run Code Online (Sandbox Code Playgroud)

我如何随机化/随机播放?

Chr*_*heD 1398

事实上的无偏差洗牌算法是Fisher-Yates(aka Knuth)Shuffle.

请参阅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];
arr = shuffle(arr);
console.log(arr);
Run Code Online (Sandbox Code Playgroud)

有关所用算法的更多信息.

  • @RobG上面的实现在功能上是正确的.在Fisher-Yates算法中,循环并不意味着为数组中的第一个元素运行.查看[wikipedia](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle),其他实现也跳过第一个元素.另请查看[this](http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html)文章,该文章讨论了为什么循环不运行第一个很重要元件. (38认同)
  • @nikola"根本不随意"对我来说有点强.我认为除非你是一个密码学家,否则它是随机的,在这种情况下你可能不会首先使用Math.Random(). (30认同)
  • @prometheus,除非连接到昂贵的硬件,否则所有RNG都是伪随机的. (22认同)
  • 呃,尤达(`0!== currentIndex`). (14认同)
  • 上面的答案跳过元素0,条件应该是`i - `不是`--i`.此外,测试`if(i == 0)...`是多余的,因为如果`i == 0`则*while*while永远不会输入.使用`... |可以更快地调用`Math.floor` 0`.可以删除*tempi*或*tempj*,并根据需要将值直接分配给*myArray [i]*或*j*. (12认同)
  • 我有点惊讶这是最佳答案。实际上有很多错误......作用域不正确,忽略简单地使用`for`循环,错误地使用`!=`和`!==`,如果传递空数组则无限循环,以及修改和返回一个参数。 (10认同)
  • 这是Fisher-Yates算法的CoffeeScript实现:https://gist.github.com/859699 (4认同)
  • 任何打算使用此实现的人:不要忘记这会改变给定的数组!这不会创建一个新的并返回一个新数组!.. (4认同)
  • @student这是一个随机的随机播放.对于包含4个元素的数组,只有24个排列.因此,对于给定的随机播放,另一个随机播放具有相同的1/24的机会.在25次洗牌之后,您将保证至少有一对重复的结果. (3认同)
  • 为什么`arr = shuffle(arr);`而不仅仅是`shuffle(arr);`?数组通过引用传递,因此您可以保留原始引用。 (2认同)
  • 如果您不想修改旧数组,请在第一行添加`var clone = array.slice(0);`,然后返回`clone`数组. (2认同)

Lau*_*lst 653

这是Durstenfeld shuffle的JavaScript实现,这是Fisher-Yates的计算机优化版本:

/**
 * Randomize array element order 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Fisher-Yates算法通过为每个原始数组元素选择一个随机元素,然后从下一个绘制中排除它来工作.就像从一副牌中随机挑选一样.

这种排除是以巧妙的方式完成的(由Durstenfeld发明供计算机使用),方法是将拾取的元素与当前元素交换,然后从剩余部分中挑选下一个随机元素.为了获得最佳的效率,循环向后运行,这样随便挑简化(它可以随时从0开始),而且因为没有其他的选择了它跳过最后一个元素.

该算法的运行时间为O(n).请注意,shuffle是就地完成的.因此,如果您不想修改原始数组,请首先复制它.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]];
    }
}
Run Code Online (Sandbox Code Playgroud)

  • ps与ChristopheD的答案相同的算法,但有解释和更清晰的实现. (21认同)
  • 人们将错误的人归因于算法.这不是费雪 - 耶茨洗牌,而是[**Durstenfeld shuffle**](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm).真正的原始Fisher-Yates算法在n ^ 2次运行,而不是n次 (12认同)
  • @MarjanVenema不确定你是否还在观看这个空间,但这个答案*是正确的,而你所暗示的改变实际上会引入偏见.有关此错误的详细说明,请参阅https://blog.codinghorror.com/the-danger-of-naivete/. (11认同)
  • 它不需要`返回数组',因为JavaScript在用作函数参数时通过引用传递数组.我假设这是为了节省堆栈空间,但这是一个有趣的小功能.在阵列上执行shuffle将改组原始阵列. (7认同)
  • 这个答案中的实现有利于数组的低端.[发现困难的方式](http://softwareonastring.com/1135/perils-of-copy-paste-programming).`Math.random()不应该与循环计数器+ 1相乘,而应该与`array.lengt()`相乘.请参阅[在特定范围内的JavaScript中生成随机整数?](http://stackoverflow.com/a/1527820/11225)以获得非常全面的解释. (5认同)
  • 您是否忘记添加“return array;”? (5认同)
  • @IRvanFauziE 该函数适用于您传递给它的数组。添加“返回数组”只会更容易与其他 . (5认同)
  • 这需要一个`return array;`结尾? (4认同)
  • @MartinBurch这里的版本不会修改原始数组并返回一个新数组.https://gist.github.com/nikolas/96586a0b56f53eabfd6fe4ed59fecb98 (2认同)
  • 重复 user94559 的评论并引用 https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 要交换的元素 (j) 应该在 0 和当前数组索引 (i) 之间 (2认同)

dea*_*unk 125

[社区编辑:这个答案是不正确的; 看评论.它留在这里供将来参考,因为这个想法并不罕见.]

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

  • 但它很可爱 (229认同)
  • Downvoting因为这不是那么随意.我不知道为什么会有这么多的赞成.不要使用这种方法.它看起来很漂亮,但并不完全正确.以下是10,000次迭代后数据中每个数字击中指数[0]的次数的结果(我也可以给出其他结果):1 = 29.19%,2 = 29.53%,3 = 20.06%,4 = 11.91%, 5 = 5.99%,6 = 3.32% (135认同)
  • 它也是[所有可用方法中效率最低的](http://jsperf.com/array-shuffle-comparator/5). (21认同)
  • 我喜欢这个解决方案,足以给出基本的随机性 (12认同)
  • 问题是它不是确定性的,会给出错误的结果(如果1> 2和2> 3,应该给出1> 3,但这不能保证.这会混淆排序,并给出结果评论由@radtad). (12认同)
  • 如果你需要随机化相对较小的数组而不处理加密的东西,这很好.我完全同意,如果你需要更多*随机*,你需要使用更复杂的解决方案. (5认同)
  • 基本阅读:[使用JavaScript Array.sort()方法进行改组是否正确?](http://stackoverflow.com/q/962802/1048572) (5认同)
  • @moeiscool jQuery究竟是如何与这个答案相关的? (4认同)
  • http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html (3认同)
  • sort()随机化一个数组有一个强烈的偏见,即除了数组的边缘比中间偏差稍大一些之外,保持每个元素在其初始起始位置:http://jsfiddle.net/rcmp0aLL/ (2认同)
  • 由于它的简单性,我没有经过太多测试就投票了 (2认同)

con*_*con 74

人们可以(或应该)将它用作Array的原型:

来自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;
}
Run Code Online (Sandbox Code Playgroud)

  • 人们可以(或应该)避免扩展原生原型:https://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/ (54认同)
  • 真的没有好处,IMOHO,除了可能踩到别人的实施.. (39认同)
  • @TinyGiant实际上:不要使用`for ... in`循环迭代数组. (18认同)
  • 你不应该这样做; 受此影响的每个数组都不能再使用for ... in来安全地迭代.不要扩展原生原型. (10认同)
  • 如果在Array原型中使用,则应该将其命名为*shuffle*. (2认同)

小智 62

使用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();
Run Code Online (Sandbox Code Playgroud)

  • @frabcus:包含整个库只是为了获得一个`shuffle`函数是没有意义的. (58认同)
  • @Blender:我给出了一个理由.1)我向你保证,你可以在你编写的任何代码中引入一个错误,无论它多么微不足道.为何冒风险?2)不要预先优化.3)99%的时间你需要一个随机算法,你的应用程序不是写一个随机算法.它是关于*需要*一个洗牌算法的东西.利用他人的工作.除非必须,否则不要考虑实施细节. (17认同)
  • 很棒的答案!谢谢.我更喜欢其他答案,因为它鼓励人们使用库,而不是在任何地方复制和粘贴潜在的错误功能. (12认同)
  • 我不同意@Blender.包含整个库只是为了获得所需的功能有很多原因.其中之一是当你自己编写bug时,出现bug的风险较小.**如果**是性能问题,那么你不应该使用它.但仅仅因为它*可能*是一个性能问题并不意味着它会. (10认同)
  • @tieTYT:那么为什么你需要库的其余部分?Fisher-Yates shuffle很难实现.你不需要一个库来从一个数组中挑选一个随机元素(我希望),所以没有理由使用一个库,除非你真的要使用它中的多个函数. (7认同)

sup*_*ary 56

您可以使用地图和排序轻松完成:

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)
Run Code Online (Sandbox Code Playgroud)
  1. 我们将数组中的每个元素放在一个对象中,并给它一个随机的排序键
  2. 我们使用随机密钥进行排序
  3. 我们取消映射以获取原始对象

您可以随机播放多态数组,并且排序与Math.random一样随机,这对于大多数用途来说已经足够了.

由于元素是针对每次迭代不重新生成的一致键进行排序的,并且每次比较都来自同一分布,因此Math.random分布中的任何非随机性都会被取消.

  • 出于多种原因,这是这里的最佳答案(对于短数组)。对我来说,它真的很有用,因为我将在 2021 年使用 React,它最适合像这样的函数式方法。 (18认同)
  • @IljaKO - O(2N + 2(N log N)) 简化为 O(N log N),所以这确实是 O(N log N)。大 O 表示法是关于最大比例因子的。我们删除常量,因为它们不随输入大小缩放,并简化为最大的单个缩放因子。大 O 符号故意不关注细节。 (9认同)
  • 非常好。这是js中的[Schwartzian转换](https://en.wikipedia.org/wiki/Schwartzian_transform)。 (3认同)
  • 再次考虑一下复杂性,如果你必须映射 2 次,它已经遍历了元素 N 两次,而且这还不考虑 JS 的“.sort”算法的快速排序复杂性 (2认同)

coc*_*cco 48

新!

更短且可能更快的Fisher-Yates shuffle算法

  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
}
Run Code Online (Sandbox Code Playgroud)

脚本大小(使用fy作为函数名称):90bytes

演示 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
}
Run Code Online (Sandbox Code Playgroud)

在其他地方缓存var rnd=Math.random然后使用rnd()也会略微增加大数组的性能.

http://jsfiddle.net/vvpoma8w/2/

可读版本(使用原始版本.这是较慢的,vars是无用的,如闭包&";",代码本身也更短...也许读这个如何'缩小'Javascript代码,顺便说一句你不能在上面的javascript minifiers中压缩以下代码.)

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
 }
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,很好,但这并不是让它变得难以理解的原因. (14认同)
  • js是一种接受许多快捷方式和不同方式来编写它的语言..虽然这里有许多慢速可读的函数,但我想表明它是如何以更高效的方式完成的,同时也节省了一些字节...按位和这里的速记真的被低估了,网络上充满了错误和缓慢的代码. (10认同)
  • 检查性能...在大多数浏览器上快2倍...但需要更多的jsperf测试人员...... (6认同)
  • 取消高尔夫,我们将给予更多投票 (4认同)
  • 并且缩小器不能正常工作.... http://stackoverflow.com/a/21353032/2450730 (2认同)

Kri*_*ekk 41

对于小型数组来说,一种非常简单的方法就是:

const someArray = [1, 2, 3, 4, 5];

someArray.sort(() => Math.random() - 0.5);
Run Code Online (Sandbox Code Playgroud)

它可能效率不高,但对于小型阵列,这种方法效果很好.这是一个例子,你可以看到它是如何随机(或不是),以及它是否适合你的用例.

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));
Run Code Online (Sandbox Code Playgroud)
<h1>Randomize!</h1>
<button id="trigger">Generate</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>
Run Code Online (Sandbox Code Playgroud)

  • 当你需要对数组进行加扰时,这是一个很好的,简单的单线程,但是不要太在意将结果在学术上证明是随机的.有时,完美的最后几英寸需要花费更多的时间而不是它的价值. (5认同)
  • 出于与/sf/answers/1305511861/中解释的相同的原因.这更有可能将早期元素留在阵列的开头附近. (4认同)

Ben*_*arp 32

使用ES6语法可以缩短一些答案.

ES6纯粹,迭代

    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]]
        }
    }
Run Code Online (Sandbox Code Playgroud)

我个人使用这个功能,因为它是纯粹的,相对简单,根据我在谷歌Chrome上的测试最有效(与其他纯版本相比).

Shuffle Array到位

    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
    };
Run Code Online (Sandbox Code Playgroud)

可靠性和性能

正如您在本页中所看到的,过去提供的解决方案不正确.因此,考虑到可靠性和性能,我编写了以下函数来测试任何纯(无副作用)数组随机化函数.我用它来测试这个答案中提出的所有选项.

    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}`)
    }
Run Code Online (Sandbox Code Playgroud)

Typescript - 用于纯数组随机化函数的类型

您可以使用以下任一方法.

    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))];
    };
Run Code Online (Sandbox Code Playgroud)

其他选择

ES6纯,递归

    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]
        })
    }
Run Code Online (Sandbox Code Playgroud)

此版本的效率低于迭代纯版本.

ES6 Pure使用array.map

    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]
        )
    }
Run Code Online (Sandbox Code Playgroud)

此版本的效率略低于迭代纯版本.

ES6 Pure使用array.reduce

    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]]
        }
    }
Run Code Online (Sandbox Code Playgroud)

此版本的效率略低于迭代纯版本.


Raf*_*nig 27

警告!
不建议将此答案用于随机化大型数组、密码学或任何其他需要真正随机性的应用程序,因为它存在偏差且效率低下。元素位置只是半随机的,它们往往会更接近其原始位置。请参阅/sf/answers/1305511861/


1 : -1您可以通过以下方式任意决定是否返回Math.random

[1, 2, 3, 4].sort(() => (Math.random() > 0.5) ? 1 : -1)
Run Code Online (Sandbox Code Playgroud)

尝试运行以下示例:

[1, 2, 3, 4].sort(() => (Math.random() > 0.5) ? 1 : -1)
Run Code Online (Sandbox Code Playgroud)


hak*_*iko 25

//one line solution
shuffle = (array) => array.sort(() => Math.random() - 0.5);


//Demo
let arr = [1, 2, 3];
shuffle(arr);
alert(arr);
Run Code Online (Sandbox Code Playgroud)

https://javascript.info/task/shuffle

Math.random() - 0.5 是一个可能为正数或为负数的随机数,因此排序功能会随机对元素进行重新排序。

  • 这也是[这个旧答案的重复](/sf/answers/3026504631/)。和[这个更旧的答案](/sf/answers/1305511861/)不需要重复一个糟糕的算法。 (7认同)

Kin*_*rog 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
};
Run Code Online (Sandbox Code Playgroud)

  • @DavidJones:为什么我要包含一个完整的4kb库只是为了洗牌? (42认同)
  • 我们应该鼓励人们使用_.shuffle而不是从堆栈溢出中粘贴代码; 而且,我们应该阻止人们压缩他们的堆栈溢出答案.这就是jsmin的用途. (3认同)
  • 在循环中执行`var b =`而不是在循环中声明b并在循环中用`b =`赋值? (2认同)
  • @Brian*不会*有所作为; 解析源代码时会发生提升.不可能涉及. (2认同)

Bru*_*oLM 16

使用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;
}
Run Code Online (Sandbox Code Playgroud)

用法:

[1, 2, 3, 4, 5, 6, 7].shuffle();
Run Code Online (Sandbox Code Playgroud)

  • 要截断,你应该使用`n >>> 0`而不是`~~ n`.阵列指数可高于2³¹-1. (4认同)

Top*_*phe 15

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;
};
Run Code Online (Sandbox Code Playgroud)


Dan*_*tin 15

我发现这个变体在"被作者删除"的答案中悬而未决.与已经有许多赞成票的其他答案不同,这是:

  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] });
}
Run Code Online (Sandbox Code Playgroud)

  • 你需要使用`.sort(function(a,b){return a [0] - b [0];})`如果你想要排序以数字方式比较值.默认的`.sort()`比较器是lexicographic,这意味着它会认为`10`小于'2`,因为`1`小于'2`. (3认同)

Alj*_*aro 11

这是最简单的一个,

function shuffle(array) {
  return array.sort(() => Math.random() - 0.5);
}
Run Code Online (Sandbox Code Playgroud)

进一步的例子,你可以在这里查看

  • 更不用说[这个答案](/sf/ask/171566811/#59075967)。无需重复...此外,该方法不提供均匀的概率分布。 (4认同)
  • 看起来很像[这个旧答案](/sf/answers/4135317721/)... (2认同)

Tín*_*ang 10

您可以通过以下方式轻松完成:

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

请参阅JavaScript Sorting Arrays

  • 您可以在https://css-tricks.com/snippets/javascript/shuffle-array/或https://news.ycombinator.com/item?id=2728914阅读该线程。W3schools一直是而且仍然是可怕的信息来源。 (3认同)

Jul*_*ian 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));
};
Run Code Online (Sandbox Code Playgroud)


dpa*_*tru 8

Fisher-Yates在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);
  }
}
Run Code Online (Sandbox Code Playgroud)


Evg*_*ova 8

一个不改变源数组的shuffle函数

更新:这里我建议一个相对简单(不是从复杂性的角度来看)和算法,它可以很好地处理小型数组,但是当你处理大型数组时,它肯定会比经典的Durstenfeld算法花费更多.你可以在这个问题的最热门回复中找到Durstenfeld.

原始答案:

如果您不希望您的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;
}
Run Code Online (Sandbox Code Playgroud)

随机逻辑:拾取随机索引,然后将相应的元素添加到结果数组中,并将其从源数组副本中删除.重复此操作,直到源数组变.

如果你真的想要它简短,这里有多远:

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;
}
Run Code Online (Sandbox Code Playgroud)


Tha*_*you 8

基准

让我们先看看结果,然后我们将看看shuffle下面的每个实现-

  • 拼接

  • 流行音乐

  • 到位


拼接很慢

任何使用spliceshift在循环中的解决方案都会非常慢。当我们增加数组的大小时,这一点尤其明显。在一个朴素的算法中,我们 -

  1. 在输入数组中获得一个rand位置it
  2. 添加t[i]到输出
  3. splicei数组中的位置t

为了夸大缓慢的效果,我们将在包含一百万个元素的数组上演示这一点。以下脚本将近 30 秒-

const shuffle = t =>
  Array.from(sample(t, t.length))

function* sample(t, n)
{ let r = Array.from(t)
  while (n > 0 && r.length)
  { const i = rand(r.length) // 1
    yield r[i]               // 2
    r.splice(i, 1)           // 3
    n = n - 1
  }
}

const rand = n =>
  Math.floor(Math.random() * n)

function swap (t, i, j)
{ let q = t[i]
  t[i] = t[j]
  t[j] = q
  return t
}

const size = 1e6
const bigarray = Array.from(Array(size), (_,i) => i)
console.time("shuffle via splice")
const result = shuffle(bigarray)
console.timeEnd("shuffle via splice")
document.body.textContent = JSON.stringify(result, null, 2)
Run Code Online (Sandbox Code Playgroud)
body::before {
  content: "1 million elements via splice";
  font-weight: bold;
  display: block;
}
Run Code Online (Sandbox Code Playgroud)


流行很快

诀窍splice不是使用超级高效的pop. 要做到这一点,代替典型的splice电话,你 -

  1. 选择要拼接的位置, i
  2. t[i]与最后一个元素交换,t[t.length - 1]
  3. 添加t.pop()到结果中

现在我们可以shuffle不到 100 毫秒的时间内处理一百万个元素——

const shuffle = t =>
  Array.from(sample(t, t.length))

function* sample(t, n)
{ let r = Array.from(t)
  while (n > 0 && r.length)
  { const i = rand(r.length) // 1
    swap(r, i, r.length - 1) // 2
    yield r.pop()            // 3
    n = n - 1
  }
}

const rand = n =>
  Math.floor(Math.random() * n)

function swap (t, i, j)
{ let q = t[i]
  t[i] = t[j]
  t[j] = q
  return t
}

const size = 1e6
const bigarray = Array.from(Array(size), (_,i) => i)
console.time("shuffle via pop")
const result = shuffle(bigarray)
console.timeEnd("shuffle via pop")
document.body.textContent = JSON.stringify(result, null, 2)
Run Code Online (Sandbox Code Playgroud)
body::before {
  content: "1 million elements via pop";
  font-weight: bold;
  display: block;
}
Run Code Online (Sandbox Code Playgroud)


甚至更快

shuffle上面的两个实现产生了一个新的输出数组。输入数组未修改。这是我首选的工作方式,但是您可以通过原地改组来进一步提高速度。

shuffle不到 10 毫秒的时间内将低于100 万个元素-

function shuffle (t)
{ let last = t.length
  let n
  while (last > 0)
  { n = rand(last)
    swap(t, n, --last)
  }
}

const rand = n =>
  Math.floor(Math.random() * n)

function swap (t, i, j)
{ let q = t[i]
  t[i] = t[j]
  t[j] = q
  return t
}

const size = 1e6
const bigarray = Array.from(Array(size), (_,i) => i)
console.time("shuffle in place")
shuffle(bigarray)
console.timeEnd("shuffle in place")
document.body.textContent = JSON.stringify(bigarray, null, 2)
Run Code Online (Sandbox Code Playgroud)
body::before {
  content: "1 million elements in place";
  font-weight: bold;
  display: block;
}
Run Code Online (Sandbox Code Playgroud)


Mil*_*dek 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)];
  });
}
Run Code Online (Sandbox Code Playgroud)

编辑:正如@gregers所指出的,比较函数是用值而不是索引调用的,这就是你需要使用的原因indexOf.请注意,此更改使得代码不太适合indexOf在O(n)时间内运行的较大数组.


iPh*_*ney 7

对于我们这些不太有天赋但能够接触到 lodash 奇迹的人来说,有一个像lodash.shuffle这样的东西。


Yev*_*kov 6

2019 年我们仍在改组数组,所以这是我的方法,对我来说似乎既简洁又快速

const src = [...'abcdefg'];

const shuffle = arr => 
  [...arr].reduceRight((res,_,__,s) => 
    (res.push(s.splice(0|Math.random()*s.length,1)[0]), res),[]);

console.log(shuffle(src));
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper {min-height: 100%}
Run Code Online (Sandbox Code Playgroud)


Eri*_*dán 6

使用Fisher-Yates shuffle 算法和 ES6:

// Original array
let array = ['a', 'b', 'c', 'd'];

// Create a copy of the original array to be randomized
let shuffle = [...array];

// Defining function returning random value from i to N
const getRandomValue = (i, N) => Math.floor(Math.random() * (N - i) + i);

// Shuffle a pair of two elements at random position j
shuffle.forEach( (elem, i, arr, j = getRandomValue(i, arr.length)) => [arr[i], arr[j]] = [arr[j], arr[i]] );

console.log(shuffle);
// ['d', 'a', 'b', 'c']
Run Code Online (Sandbox Code Playgroud)


小智 6

我发现这很有用:

const shuffle = (array: any[]) => {
    return array.slice().sort(() => Math.random() - 0.5);
  }
        
console.log(shuffle([1,2,3,4,5,6,7,8,9,10]));
// Output: [4, 3, 8, 10, 1, 7, 9, 2, 6, 5]
Run Code Online (Sandbox Code Playgroud)


Rap*_*l C 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;
}
Run Code Online (Sandbox Code Playgroud)


vic*_*sys 5

随机化数组

 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; 
Run Code Online (Sandbox Code Playgroud)


abu*_*ick 5

对CoolAJ86 答案的简单修改,不会修改原始数组:

 /**
 * Returns a new array whose contents are a shuffled copy of the original array.
 * @param {Array} The items to shuffle.
 * /sf/answers/171568351/
 * /sf/answers/3084992151/
 */
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;
};
Run Code Online (Sandbox Code Playgroud)


Red*_*edu 5

只是为了指责派。在这里,我提出了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])));
Run Code Online (Sandbox Code Playgroud)

编辑:上面的代码是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");
Run Code Online (Sandbox Code Playgroud)

问题是,JS无法应对大型递归。在这种情况下,根据您的浏览器引擎和一些未知事实,您将数组大小限制在3000〜7000之间。


Mar*_*ski 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));
Run Code Online (Sandbox Code Playgroud)


icl*_*126 5

使用ES6功能的现代短内联解决方案:

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

(出于教育目的)


Haf*_*man 5

尽管已经建议了许多实现,但是我觉得我们可以使用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)
Run Code Online (Sandbox Code Playgroud)


Gia*_*dei 5

我使用这两种方法:

该方法不会修改原数组

shuffle(array);
Run Code Online (Sandbox Code Playgroud)

该方法修改原数组

array.shuffle();
Run Code Online (Sandbox Code Playgroud)

function shuffle(arr) {
    var len = arr.length;
    var d = len;
    var array = [];
    var k, i;
    for (i = 0; i < d; i++) {
        k = Math.floor(Math.random() * len);
        array.push(arr[k]);
        arr.splice(k, 1);
        len = arr.length;
    }
    for (i = 0; i < d; i++) {
        arr[i] = array[i];
    }
    return arr;
}

var arr = ["a", "b", "c", "d"];
arr = shuffle(arr);
console.log(arr);
Run Code Online (Sandbox Code Playgroud)


May*_*ela 5

您可以使用lodash随机播放。奇迹般有效

import _ from lodash;

let numeric_array = [2, 4, 6, 9, 10];
let string_array = ['Car', 'Bus', 'Truck', 'Motorcycle', 'Bicycle', 'Person']

let shuffled_num_array = _.shuffle(numeric_array);
let shuffled_string_array = _.shuffle(string_array);

console.log(shuffled_num_array, shuffled_string_array)
Run Code Online (Sandbox Code Playgroud)


And*_*rks 5

使用生成器函数的 ES6 紧凑代码*

其工作原理是从未洗牌的数组的副本中随机删除项目,直到没有剩余的为止。它使用新的 ES6生成器函数。

只要 Math.random() 是公平的,这将是一个完全公平的洗牌。

let arr = [1,2,3,4,5,6,7]

function* shuffle(arr) {
  arr = [...arr];
  while(arr.length) yield arr.splice(Math.random()*arr.length|0, 1)[0]
}

console.log([...shuffle(arr)])
Run Code Online (Sandbox Code Playgroud)

或者,使用 ES6 和 splice:

let arr = [1,2,3,4,5,6,7]

let shuffled = arr.reduce(([a,b])=>
  (b.push(...a.splice(Math.random()*a.length|0, 1)), [a,b]),[[...arr],[]])[1]

console.log(shuffled)
Run Code Online (Sandbox Code Playgroud)

或者,ES6索引交换方法:

let arr = [1,2,3,4,5,6,7]

let shuffled = arr.reduce((a,c,i,r,j)=>
  (j=Math.random()*(a.length-i)|0,[a[i],a[j]]=[a[j],a[i]],a),[...arr])

console.log(shuffled)
Run Code Online (Sandbox Code Playgroud)