如何随机化(随机播放)JavaScript数组?


1263

我有一个像这样的数组:

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

Answers:


1538

实际无偏混洗算法是Fisher-Yates(aka Knuth)混洗。

参见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)...是多余的,因为如果i == 0同时循环将永远不会被输入。Math.floor使用可以更快地完成对的调用...| 0。任一拍子tempj可以被移除并且该值被直接分配给myArray的[I]Ĵ适当。
RobG 2011年

23
@prometheus,除非连接到昂贵的硬件,否则所有RNG都是伪随机的。
Phil H

38
@RobG上面的实现在功能上是正确的。在Fisher-Yates算法中,循环并不意味着要在数组中的第一个元素上运行。查看Wikipedia,那里还有其他实现也跳过第一个元素的实现。还检查了物品,其谈到为什么回路不能为第一元素运行是非常重要的。
2012年

34
@nikola“一点也不随意”对我来说是一个很强的资格。我认为除非您是密码学家,否则它是足够随机的,在这种情况下,您可能首先没有使用Math.Random()。
toon81 2013年

20
gh,友田(0 !== currentIndex)。
ffxsam

744

这是Durstenfeld shuffle的JavaScript实现,它是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与ChristopheD的答案相同的算法,但有解释和更简洁的实现。
劳伦斯·霍尔斯特

12
人们将错误的人归因于算法。这不是费舍尔·耶茨的洗牌,而是杜斯滕菲尔德的洗牌。真正的原始Fisher-Yates算法运行时间为n ^ 2,而不是n时间
Pacerier 2014年

7
return array由于JavaScript在用作函数参数时通过引用传递数组,因此不需要这样做。我认为这是为了节省堆栈空间,但这是一个有趣的小功能。在阵列上执行随机播放将随机播放原始阵列。
乔尔·特劳格

5
此答案中的实现偏爱数组的下端。找出了艰难的道路Math.random() should not be multiplied with the loop counter + 1, but with array.lengt()`。请参阅在特定范围内的JavaScript中生成随机整数?以获得非常全面的解释。
Marjan Venema

13
@MarjanVenema不知道您是否还在看这个空间,但是这个答案正确的,并且您建议的更改实际上会带来偏差。有关此错误的详细记录,请参见blog.codinghorror.com/the-danger-of-naivete
4559年

133

警告!不建议
使用此算法,因为它效率低下并且存在严重偏差;看评论。它被留在这里以备将来参考,因为这种想法并不罕见。

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

13
我喜欢这种解决方案,足以提供基本的随机性
Alex K

147
降低投票率并不是真正随机的事情。我不知道为什么会有这么多的投票。不要使用此方法。它看起来很漂亮,但并不完全正确。以下是经过10,000次迭代后得出的结果:数组中的每个数字击中索引[0]的次数(我也可以给出其他结果):1 = 29.19%,2 = 29.53%,3 = 20.06%,4 = 11.91%, 5 = 5.99%,6 = 3.32%
radtad

8
如果您需要将相对较小的数组随机化而不处理加密事物,那就很好。我完全同意,如果需要更多随机性,则需要使用更复杂的解决方案。
deadrunk


12
问题在于它不是确定性的,将导致错误的结果(如果1> 2和2> 3,则应假定1> 3,但这不能保证这一点。这会使排序混乱,并给结果加注释通过@radtad)。
MatsLindh 2014年

73

一个人可以(或应该)将其用作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;
}

42
对此,IMOHO真的没有任何好处,除了可能踩踏别人的实现..
user2864740

2
如果在Array原型中使用,则应命名为shuffle以外的名称
狼”

57
人们可以(或应该)避免延长本机原型:javascriptweblog.wordpress.com/2011/12/05/...
Wédney尤里·

12
你不应该这样做 受此影响的每个数组不再可以使用for ... in安全地迭代。不要扩展本机原型。

18
@TinyGiant实际上:不要使用for...in循环来遍历数组。
Conor O'Brien 2015年

69

您可以使用map和sort轻松完成此操作:

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洗牌那样有效,但是在我看来,代码明显更短且功能更多。如果阵列很大,则一定要使用Fischer Yates。如果您的阵列很小,只有几百个项目,则可以这样做。


1
@superluminary糟糕,您是对的。请注意,此答案已使用相同的方法。
Bergi

@Bergi-是的,您是对的,尽管我认为我的实现有点漂亮。
superluminary

3
非常好。这是js中的Schwartzian转换
Mark Grimes

@torazaburo-它不像Fischer Yates那样出色,但是更漂亮,并且代码更小。代码始终是一个折衷方案。如果我有很多阵列,我会使用Knuth。如果我有几百个物品,我会这样做。
superluminary '18

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 2013年

60
@frabcus:仅仅为了获得一个shuffle功能而包括整个库是没有意义的 。
搅拌器

11
我不同意@Blender。包含整个库只是出于获得所需功能的多种原因。其中之一是,您自己编写bug的风险较小。 如果这是性能问题,则不应使用它。但是仅仅因为它可能是一个性能问题并不意味着它会。
Daniel Kaplan

7
@tieTYT:那为什么还需要其余的库呢?Fisher-Yates改组很容易实现。您不需要库就可以从数组中选择随机元素(我希望如此),因此除非您实际上打算从库中使用多个函数,否则没有理由使用库。
搅拌器

18
@Blender:我给出了一个原因。1)我向您保证,无论多么琐碎,您都可以在所编写的任何代码中引入错误。为什么要冒险呢?2)不要预先优化。3)99%的时间,当您需要改组算法时,您的应用程序不是要编写改组算法。这是关于需要改组算法的事情。利用他人的工作。除非必须,否则不要考虑实现细节。
丹尼尔·卡普兰

50

新!

更快或更短的* Fisher-Yates随机播放算法

  1. 它使用while-
  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作为函数名):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
}

缓存到其他地方var rnd=Math.random然后使用rnd()也会稍微提高大阵列的性能。

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

可读的版本(使用原始版本。速度较慢,vars没用,像闭包&“;”一样,代码本身也更短...也许阅读此方法如何“最小化” 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 2014年

扣篮性能没有提高。交换fyand shuffle prototype,我fy在OS X 10.9.5的Chrome 37中始终处于最底层(与20万ops相比,约20k ops降低了81%)和Safari 7.1,我的Safari 7.1降低了约8%。YMMV,但并不总是更快。 jsperf.com/fyshuffle/3
Spig,2014年

再次检查统计信息...我已经写出chrome较慢,因为它们优化了Math,在所有其他按位上又更快。检查IE,firefox以及移动设备。也很高兴看到歌剧...
cocco 2014年

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

不太确定我是否理解正确。每次您调用sort数组时,这种方法的确会以随机方式(尽管是伪随机)对数组进行混洗-出于明显的原因,它不是稳定的排序。
克里斯·塞尔贝克

4
出于与stackoverflow.com/a/18650169/28234所述相同的原因。这很可能使早期元素靠近数组的开头。
AlexC

7
当您需要对数组进行加扰时,这是一种很好的简便方法,但是不必太在乎结果在学术上可证明是随机的。有时,最后几英寸达到完美需要花费更多的时间。
丹尼尔·格里斯康

1
如果能奏效的话会很可爱,但事实并非如此。由于快速搜索的工作方式,不一致的比较器可能会将数组元素保留在其原始位置附近。您的阵列不会被打乱。
superluminary

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 Pure,迭代式

    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

@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的目的。
David Jones

45
@DavidJones:为什么我要包括整个4kb的库只是为了随机排列数组?
Blender

1
@KingKongFrog的名字调用也不利于一个合理社区的聚集。
麦迪

2
其高效做var b = 在一个循环中宣布31b的外侧环路,并分配给它,而不是b = 在一个循环?
Alex K

2
@Brian 不会有所作为;解析源代码时发生吊装。可能没有涉及。
user2864740 2014年

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。
Oriol

1
这样的破坏使这种干净的实现
成为

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
Daniel Martin

3
.sort(function(a,b){ return a[0] - b[0]; })如果要排序以数字方式比较值,则需要使用。默认.sort()比较器是lexicographic,这意味着它将被认为10小于,2因为1小于2
4castle

@ 4castle好的,我更新了代码,但要还原它:字典顺序和数字顺序之间的区别对于Math.random()产生范围内的数字并不重要。(也就是说,字典序与数字序在处理从0(含)到1(不含)的数字时是相同的)
Daniel Martin

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

显然,这不像Fisher-Yates算法那样理想,但是对技术面试有用吗?
davidatthepark '16

@Andrea代码由于在for循环内更改了数组长度而被破坏。上一次编辑后,此问题已得到纠正。
查理·华莱士

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

1
为什么要减去0.5?这个数字是什么意思?
Sartheris Stormhammer

1
@SartherisStormhammer,因为我们正在使用compareFunction进行排序,如果返回的数字大于0,则被比较的元素将仅按方向排序。Math.random()上的-0.5将给我们约50%的时间负数,这给了我们相反的顺序。
Sam Doidge

简单直接的解决方案。谢谢
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的
静省午光

4
您可以在css-tricks.com/snippets/javascript/shuffle-arraynews.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

Fisher-Yates在javascript中随机播放。我将其发布在这里是因为与此处的其他答案相比,使用两个实用程序功能(交换和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所指出的,compare函数是使用值而不是索引来调用的,这就是为什么需要使用indexOf。请注意,此更改使代码indexOf在O(n)时间内运行时不太适合大型数组。


Array.prototype.sort传入两个 as ab,而不是索引。因此此代码不起作用。
gregers,

@gregers你是对的,我已经编辑了答案。谢谢。
Milo Wielondek '16

1
这不是很随机。根据排序的实现方式,数组最低索引处的元素可能需要比最高索引旁的元素更多的比较才能获得最高索引。这意味着最低索引的元素不太可能到达最高索引。
1'或1-

7

一个不改变源数组的随机播放功能

更新:在这里,我建议一个相对简单的算法(不是从复杂性的角度来看),并且简短的算法可以很好地处理小型数组,但是当您处理大型数组时,它的成本肯定会比经典的Durstenfeld算法高得多。您可以在对此问题的最高答复之一中找到Durstenfeld

原始答案:

如果您不希望 shuffle函数更改源数组,可以将其复制到局部变量,然后使用简单的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;
}

这本质上是原始的Fisher-Yates算法,您splice做它们所谓的“删除”的效率极低。如果您不想突变原始数组,则只需复制它,然后使用效率更高的Durstenfeld变体将其复制到位。

@torazaburo,谢谢您的反馈。我已经更新了答案,以明确说明,我提供的是一种美观的解决方案,而不是超级扩展的解决方案
Evgenia Manolova

我们还可以使用该splice方法来创建一个副本,如下所示:source = array.slice();
大河


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/...
拉斐尔C·

嗯,您能指出参考文件中的特定内容吗?除了顶部隐约可见的关于可能使js引擎难以优化的注释之外,似乎没有任何提及“提高性能”的内容。在这种情况下,我不清楚使用strict会改善什么。
shortstuffsushi

严格模式已经存在了一段时间,那里有足够的读数供任何人提出自己的意见,不管他们是否应该始终使用它以及为什么使用它。举例来说,Jslint使得您应该始终使用严格模式非常清楚。道格拉斯·克罗克福德(Douglas Crockford)写了很多文章和一些精彩的视频,介绍了为什么始终使用严格模式不仅很重要,不仅是一种很好的实践,而且对于浏览器js引擎(例如V8)如何以不同的方式进行解释也很重要。我强烈建议您使用Google并对此发表自己的看法。
拉斐尔C

下面是关于严格模式perfs一个古老的线程,有点老,但仍然具有现实意义:stackoverflow.com/questions/3145966/...
拉斐尔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当n为你使用<<=
Mohebifar

3

最短的arrayShuffle功能

function arrayShuffle(o) {
    for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
}

显然,您正在使用Sattolo而不是Fisher-Yates(Knuth,无偏见)。
Arthur2e5

3

从理论的角度来看,我拙见认为,最优雅的方法是获取0n!-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个元素的数组,您可以放心地认为绝对不会有偏差。


绝对优雅!
Gershom
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.