ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个这样的数组:

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

如何随机化/随机播放?

只需将其放在这里,您就可以使用 Mike Bostock 制作的这个可视化器来可视化 shuffle 函数的实际随机性:bost.ocks.org/mike/shuffle/compare.html
@Blazemonger jsPref 已死。你能在这里发布最快的吗?
这个怎么样? arr1.sort(() => (Math.random() > .5) ? 1 : -1);
简短的回答是a.sort(() => Math.random() - 0.5)
@TheVee 在同一规范上看到上面几行:“排序顺序是实现定义的,如果 ...如果 comparefn 不是未定义的并且不是项目元素的一致比较函数”

2
21 revs, 17 users 36%

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

您可以看到 great visualization here(以及原始帖子 linked to this

函数 shuffle(array) { 让 currentIndex = array.length, randomIndex; // 虽然还有要洗牌的元素。 while (currentIndex != 0) { // 选择一个剩余的元素。 randomIndex = Math.floor(Math.random() * currentIndex);当前索引--; // 并将其与当前元素交换。 [数组[当前索引],数组[随机索引]] = [数组[随机索引],数组[当前索引]]; } 返回数组; } // 像这样使用 var arr = [2, 11, 37, 42];洗牌(arr);控制台.log(arr);

使用了更多信息 about the algorithm


上面的答案跳过元素 0,条件应该是 i-- 而不是 --i。此外,测试 if (i==0)... 是多余的,因为如果 i == 0 永远不会进入 while 循环。使用 ...| 0 可以更快地调用 Math.floortempitempj 都可以被移除,并根据需要将值直接分配给 myArray[i]j .
@RobG 上面的实现在功能上是正确的。在 Fisher-Yates 算法中,循环并不意味着针对数组中的第一个元素运行。查看 wikipedia,其中还有其他实现也跳过了第一个元素。另请查看 this 文章,该文章讨论了为什么不为第一个元素运行循环很重要。
如果您要在繁忙的循环中进行解构分配,请务必进行转换——分配对象的成本很高。
@ggorlen 在这种情况下转译是什么意思?您能给我们举个例子或进一步解释吗?
我有点惊讶这是最佳答案。实际上有很多事情是错误的... 范围不当,忽略了简单地使用 for 循环,错误地将 !=!== 一起使用,如果传递一个空数组,则无限循环,以及修改和返回一个范围。
a
ashleedawg

这是 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]];
    }
}

此答案中的实现有利于数组的低端。 Found out the hard wayMath.random() should not be multiplied with the loop counter + 1, but with array.lengt()`。请参阅 Generating random whole numbers in JavaScript in a specific range? 以获得非常全面的解释。
@MarjanVenema 不确定你是否还在看这个空间,但这个答案正确的,你建议的改变实际上引入了偏见。请参阅 blog.codinghorror.com/the-danger-of-naivete 以获得有关此错误的详细说明。
用引用重复 user94559 的评论 en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 要交换的元素 (j) 应该 在 0 和当前数组索引 (i) 之间
这是相同的函数,但经过压缩:function shuffle(a){for(var j,i=a.length-1;i>0;i--){j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}}
您忘记添加 return array; 了吗?
s
superluminary

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

let unshuffled = ['hello', 'a', 't', 'q', 1, 2, 3, {cats: true}]

let shuffled = unshuffled
  .map(value => ({ value, sort: Math.random() }))
  .sort((a, b) => a.sort - b.sort)
  .map(({ value }) => value)

我们将数组中的每个元素放在一个对象中,并给它一个随机排序键我们使用随机键进行排序我们取消映射以获取原始对象

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

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

速度

时间复杂度为 O(N log N),与快速排序相同。空间复杂度为 O(N)。这不如 Fischer Yates shuffle 高效,但在我看来,代码明显更短且功能更强大。如果你有一个大数组,你当然应该使用 Fischer Yates。如果你有一个包含几百个项目的小数组,你可能会这样做。


非常好。这是 js 中的 Schwartzian transform
出于多种原因,这是此处的最佳答案(对于短数组)。对我来说,它真的很有用,因为我在 2021 年使用 react,它最适合像这样的函数式方法。
如果您必须映射 2 次它已经遍历元素 N 两次,并且没有考虑 JS 的 .sort 算法的快速排序复杂性,请再次考虑复杂性
@IljaKO 2N 还是 O(N),小于 O(N log N) 的时间复杂度
不过,这一切都在细节中。我不认为他的方法是 O(N log N),而是更喜欢另一种真正 O(N log N) 的方法
R
Rick

警告!不推荐使用该算法,因为它效率低且有很强的偏见;看评论。它留在这里供将来参考,因为这个想法并不罕见。

[1,2,3,4,5,6].sort( () => .5 - Math.random() );

https://javascript.info/array-methods#shuffle-an-array 教程直截了当地解释了这些差异。


否决,因为这并不是那么随机。我不知道为什么它有这么多的赞成票。不要使用这种方法。它看起来很漂亮,但并不完全正确。以下是 10,000 次迭代后数组中每个数字命中索引 [0] 的次数的结果(我也可以给出其他结果):1 = 29.19%,2 = 29.53%,3 = 20.06%,4 = 11.91%, 5 = 5.99%,6 = 3.32%
如果您需要随机化相对较小的数组并且不处理加密事物,那很好。我完全同意,如果您需要更多随机性,则需要使用更复杂的解决方案。
问题是它不是确定性的,这会给出错误的结果(如果 1 > 2 和 2 > 3,应该给出 1 > 3,但这不能保证。这会混淆排序,并给出注释的结果通过@radtad)。
L
Lukas Liesis

可以(但不应该)将其用作 Array 的原型:

来自克里斯托夫:

Array.prototype.shuffle = function() {
  var i = this.length, j, temp;
  if ( i == 0 ) return this;
  while ( --i ) {
     j = Math.floor( Math.random() * ( i + 1 ) );
     temp = this[i];
     this[i] = this[j];
     this[j] = temp;
  }
  return this;
}

不要碰原型,除非你真的需要在整个程序中洗牌所有或大部分数组,并且你正在编写这个程序,没有人会发现它。我看到这个答案已有十年之久,可能发生在“人们,停止扩展原型,这很糟糕”的所有运动之前。 stackoverflow.com/questions/14034180/…
在 while 循环中,当您将 i 设为 0 时,它变为 false,因此忽略列表中的第一个元素,而仅对其余元素进行改组...所以第一个元素永远不会改组...扩展原型时 +1,使代码更多在我的情况下可读。
h
hexacyanide

使用 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();

C
Community

新的!

更短且可能*更快的 Fisher-Yates 洗牌算法

它使用 while--- 按位到地板(数字最多 10 个十进制数字(32 位))删除了不必要的闭包和其他东西

function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
 c=a.length;while(c)b=Math.random()*(--c+1)|0,d=a[c],a[c]=a[b],a[b]=d
}

脚本大小(以 fy 作为函数名):90 字节

演示 http://jsfiddle.net/vvpoma8w/

*可能在除 chrome 之外的所有浏览器上更快。

如果你有问题,就问吧。

编辑

是的,它更快

性能: http://jsperf.com/fyshuffle

使用投票最多的函数。

编辑有一个多余的计算(不需要--c + 1)并且没有人注意到

更短(4字节)&更快(测试它!)。

function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
 c=a.length;while(c)b=Math.random()*c--|0,d=a[c],a[c]=a[b],a[b]=d
}

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

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

可读版本(使用原始版本。这更慢,变量无用,如闭包和“;”,代码本身也更短......也许读这个 How to 'minify' Javascript code ,顺便说一句无法在上面的 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
 }
}

检查性能...在大多数浏览器上快 2 倍...但需要更多 jsperf 测试人员...
Node.js 是一种接受许多快捷方式和不同编写方式的语言。虽然这里有许多可读性很慢的函数,但我只是想展示如何以更高性能的方式完成它,同时节省一些字节......按位和速记在这里真的被低估了,网络上充满了错误和缓慢的代码。
不是扣篮的性能提升。交换 fyshuffle prototype,我在 OS X 10.9.5 上的 Chrome 37 中始终在底部得到 fy(与 ~100k 相比,慢了 81% ~20k ops)和 Safari 7.1,它慢了 ~8% . YMMV,但并不总是更快。 jsperf.com/fyshuffle/3
再次检查统计数据......我已经写过 chrome 速度较慢,因为他们优化了数学,在所有其他按位地板上,虽然速度更快。检查 IE、firefox 以及移动设备。也很高兴看到歌剧...
移动 safari 1409(fy) vs (shuffle)1253,即 31850(fy) vs (shuffle)13405
n
ns16

随机播放数组

function shuffleArr (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
};

可靠性和性能测试

此页面上的某些解决方案不可靠(它们仅部分随机化数组)。其他解决方案的效率明显较低。使用 testShuffleArrayFun(见下文),我们可以测试数组改组函数的可靠性和性能。

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,他提出了升序算法。升序算法可以用归纳法证明。
B
Ben Carp

编辑:这个答案不正确

查看评论和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);返回一些数组; }; const renderResultsToDom = (results, el) => { el.innerHTML = results.join(' '); }; buttonEl.addEventListener('click', () => renderResultsToDom(generateArrayAndRandomize(), resultsEl));

随机化!

0 1 2 3 4 5 6 7 8 9


不错,但每次都会生成一个完整的随机元素吗?
不太确定我是否理解正确。每次调用排序数组时,这种方法确实会以随机方式(尽管是伪随机)对数组进行洗牌 - 由于显而易见的原因,它不是一种稳定的排序。
出于与 stackoverflow.com/a/18650169/28234 中解释的相同原因。这更有可能将早期元素留在数组的开头附近。
当您需要打乱数组时,这是一个很棒的、简单的单行代码,但不要太在意让结果在学术上可证明是随机的。有时,最后几英寸的完美花费比它的价值更多的时间。
如果这行得通,那就太好了,但事实并非如此。由于快速搜索的工作方式,不一致的比较器可能会使数组元素靠近其原始位置。您的阵列不会被打乱。
h
hexacyanide

添加到@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
};

我们应该鼓励人们使用 _.shuffle 而不是从堆栈溢出中粘贴代码;而且,我们应该阻止人们压缩他们的堆栈溢出答案。这就是 jsmin 的用途。
@DavidJones:为什么我要包含整个 4kb 库只是为了对数组进行洗牌?
@KingKongFrog 点名也不利于建立一个合理的社区。
在循环中执行 var b = 而不是在循环外声明 b 并在循环中用 b = 分配它是否有效?
@Brian 不会有所作为;提升在解析源代码时发生。没有可能涉及。
B
BrunoLM

使用 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();

要截断,您应该使用 n >>> 0 而不是 ~~n。数组索引可以高于 2³¹-1。
像这样的解构使得这样一个干净的实现+1
D
Daniel Martin

我在这个问题的副本的“作者删除”答案中发现了这个变体。与其他一些已经有很多赞成票的答案不同,这是:

实际上是随机的 不是就地的(因此是改组的名称而不是改组) 这里还没有多个变体

Here's a jsfiddle showing it in use

Array.prototype.shuffled = function() {
  return this.map(function(n){ return [Math.random(), n] })
             .sort().map(function(n){ return n[1] });
}

(我怀疑它已被删除,因为它是随机化数组的一种非常低效的方法,特别是对于较大的数组......而接受的答案以及该答案的许多其他克隆就地随机化)。
是的,但是鉴于众所周知的错误答案仍然有一堆投票,至少应该提到一个低效但正确的解决方案。
[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); }); - 它不会随机排序,如果你使用它,你最终会感到尴尬:robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
如果您希望排序以数字方式比较值,则需要使用 .sort(function(a,b){ return a[0] - b[0]; })。默认的 .sort() 比较器是字典顺序的,这意味着它会认为 10 小于 2,因为 1 小于 2
@4castle 好的,我更新了代码,但要恢复它:字典顺序和数字顺序之间的区别对于 Math.random() 产生的范围内的数字无关紧要。 (即在处理从0(含)到1(不含)的数字时,字典顺序与数字顺序相同)
h
hakkikonu
//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 是一个随机数,可以是正数或负数,因此排序函数会随机重新排序元素。


这不会以均匀的概率分布进行洗牌。
它也是一个重复of this older answerthis still older answer 无需重复糟糕的算法。
C
Charlie Wallace
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 算法最优,但它适用于技术面试吗?
@Andrea 由于在 for 循环中更改了数组长度,因此代码被破坏了。在最后一次编辑中,此问题已得到纠正。
你没有声明你的变量,这使它们成为全局变量——而且这个函数似乎从输入数组中随机删除元素。
E
Enijar

这是最简单的一个,

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

例如,您可以检查它here


看起来很像this older answer...
更不用说this answer了。无需重复......此外,这种方法不提供均匀的概率分布。
C
Casimir et Hippolyte

基准

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

拼接很慢

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

在输入数组 t 中获取一个 rand 位置 i,将 t[i] 添加到数组 t 中的输出拼接位置 i

为了夸大缓慢的效果,我们将在包含一百万个元素的数组上演示这一点。以下脚本将近 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) 函数交换 (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) body::before { content: "100 万个元素通过拼接";字体粗细:粗体;显示:块; }

流行很快

诀窍不是splice,而是使用超级高效的pop。为此,代替典型的 splice 调用,您 -

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

现在我们可以在 不到 100 毫秒shuffle 一百万个元素 -

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) 函数交换 (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) body::before { content: "100 万个元素通过 pop";字体粗细:粗体;显示:块; }

甚至更快

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

不到 10 毫秒,低于 shuffle 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) 函数交换 (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) body::before { content: "100 万个元素到位";字体粗细:粗体;显示:块; }


J
Julian K.

递归解决方案:

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

c
coredumperror

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

i
icl7126

使用 ES6 特性的现代短内联解决方案:

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

(用于教育目的)


这个的分布是什么?
@chovy 为了解释发生了什么,我们为数组中的每个项目生成随机数,然后按该数字对项目进行排序。因此,只要您从 Math.random() 函数中获得“真正的随机”数字,您就会得到均匀分布(每个项目都有相同的机会出现在任何位置)。
M
Milo Wielondek

首先,请查看 here,以直观地比较 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 传入两个 作为 ab,而不是索引。所以这段代码不起作用。
@gregers你是对的,我已经编辑了答案。谢谢。
这不是很随机。根据排序的实现,最低数组索引处的元素可能需要比最高索引旁边的元素更多的比较才能到达最高索引。这意味着最低索引的元素不太可能到达最高索引。
M
Marcin Malinowski

所有其他答案都基于 Math.random() ,它速度快但不适合加密级别的随机化。

下面的代码使用众所周知的 Fisher-Yates 算法,同时将 Web Cryptography API 用于随机化加密级别

var d = [1,2,3,4,5,6,7,8,9,10];函数 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; } 返回一个; } console.log(shuffle(d));


E
Evgeniya Manolova

一个不改变源数组的洗牌函数

更新:在这里我建议一个相对简单(不是从复杂性的角度来看)和简短的算法,它可以很好地处理小型数组,但是当你处理大型数组时,它肯定会比经典的 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,感谢您的反馈。我已经更新了我的答案,以明确表示我宁愿提供一个漂亮的解决方案,而不是一个超级缩放的解决方案
我们还可以使用 splice 方法创建一个副本,如下所示:source = array.slice();
E
Erik Martín Jordán

使用 Fisher-Yates 洗牌算法和 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']

伟大且易于理解。
R
Rafi Henig

您可以使用 Math.random 任意决定是否返回 1 : -1

[1, 2, 3, 4].sort(() => (Math.random() > 0.5) ? 1 : -1)

尝试运行以下示例:

常量数组 = [1, 2, 3, 4]; const shuffled = array.sort(() => { const randomTrueOrFalse = Math.random() > 0.5; return randomTrueOrFalse ? 1 : -1 });控制台.log(改组);


这是公正的吗?
什么?这太没有意义了。它几乎有 0 机会保持元素完好无损(随机生成正好 0.5)
T
Tính Ngô Quang

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

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

请参考 JavaScript Sorting Arrays


这个算法早就被证明是有缺陷的。
请向我证明。我基于 w3schools
您可以在 css-tricks.com/snippets/javascript/shuffle-arraynews.ycombinator.com/item?id=2728914 阅读该主题。 W3schools 一直是并且仍然是一个可怕的信息来源。
有关为什么这不是一个好方法的详细讨论,请参阅 stackoverflow.com/questions/962802/…
Y
Yevgen Gorbunkov

我们在 2019 年仍在改组数组,所以这是我的方法,这对我来说似乎很整洁fast

常量 src = [...'abcdefg']; const shuffle = arr => [...arr].reduceRight((res,_,__,s) => (res.push(s.splice(0|Math.random()*s.length,1)[ 0]), 资源),[]); console.log(shuffle(src)); .as-console-wrapper {最小高度:100%}


B
BBaysinger

对 CoolAJ86 的 answer 的简单修改,不修改原始数组:

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

R
Raphael C

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

与接受的答案相比,添加 use strict 提供了什么价值?
要详细了解严格模式及其对性能的影响,您可以在此处阅读:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
嗯,你能指出参考文件中的具体内容吗?除了顶部关于可能使 js 引擎难以优化的模糊评论之外,似乎没有任何内容提到“提高性能”。在这种情况下,我不清楚 use strict 会改善什么。
严格模式已经存在了很长一段时间,并且有足够的读数供任何人发表自己的意见,是否应该始终使用它以及为什么。例如,Jslint 清楚地表明您应该始终使用严格模式。 Douglas Crockford 写了很多文章和一些很棒的视频,说明为什么始终使用严格模式很重要,不仅作为一种良好的做法,而且还说明了 V8 等浏览器 js 引擎如何以不同的方式解释它。我强烈建议您使用 Google 搜索并对此发表自己的看法。
这是一个关于严格模式下性能的旧线程,有点旧但仍然相关:stackoverflow.com/questions/3145966/…
v
vickisys

随机化数组

 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; 

n 不应该有 -1,因为您使用的是 < 而不是 <=
i
iPhoney

对于我们这些不是很有天赋但可以接触到 lodash 奇迹的人来说,有 lodash.shuffle 这样的东西。