我有一个这样的数组:
var arr1 = ["a", "b", "c", "d"];
如何随机化/随机播放?
arr1.sort(() => (Math.random() > .5) ? 1 : -1);
a.sort(() => Math.random() - 0.5)
事实上的无偏洗牌算法是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。
这是 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]];
}
}
Math.random() should not be multiplied with the loop counter + 1, but with
array.lengt()`。请参阅 Generating random whole numbers in JavaScript in a specific range? 以获得非常全面的解释。
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;
了吗?
您可以使用 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。如果你有一个包含几百个项目的小数组,你可能会这样做。
.sort
算法的快速排序复杂性,请再次考虑复杂性
警告!不推荐使用该算法,因为它效率低且有很强的偏见;看评论。它留在这里供将来参考,因为这个想法并不罕见。
[1,2,3,4,5,6].sort( () => .5 - Math.random() );
本 https://javascript.info/array-methods#shuffle-an-array 教程直截了当地解释了这些差异。
可以(但不应该)将其用作 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;
}
使用 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();
新的!
更短且可能*更快的 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
}
}
fy
和 shuffle prototype
,我在 OS X 10.9.5 上的 Chrome 37 中始终在底部得到 fy
(与 ~100k 相比,慢了 81% ~20k ops)和 Safari 7.1,它慢了 ~8% . YMMV,但并不总是更快。 jsperf.com/fyshuffle/3
随机播放数组
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]
)
}
[array[i], array[rand]]=[array[rand], array[i]]
?也许你可以概述它是如何工作的。为什么选择向下迭代?
编辑:这个答案不正确
查看评论和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
添加到@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
};
var b =
而不是在循环外声明 b 并在循环中用 b =
分配它是否有效?
使用 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。
我在这个问题的副本的“作者删除”答案中发现了这个变体。与其他一些已经有很多赞成票的答案不同,这是:
实际上是随机的 不是就地的(因此是改组的名称而不是改组) 这里还没有多个变体
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
。
Math.random()
产生的范围内的数字无关紧要。 (即在处理从0(含)到1(不含)的数字时,字典顺序与数字顺序相同)
//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 是一个随机数,可以是正数或负数,因此排序函数会随机重新排序元素。
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;
};
这是最简单的一个,
function shuffle(array) {
return array.sort(() => Math.random() - 0.5);
}
例如,您可以检查它here
基准
让我们先看看结果,然后再看看下面 shuffle
的每个实现 -
拼接很慢
在循环中使用 splice
或 shift
的任何解决方案都会非常缓慢。当我们增加数组的大小时,这一点尤其明显。在一个简单的算法中,我们 -
在输入数组 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 万个元素到位";字体粗细:粗体;显示:块; }
递归解决方案:
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));
};
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);
}
}
使用 ES6 特性的现代短内联解决方案:
['a','b','c','d'].map(x => [Math.random(), x]).sort(([a], [b]) => a - b).map(([_, x]) => x);
(用于教育目的)
Math.random()
函数中获得“真正的随机”数字,您就会得到均匀分布(每个项目都有相同的机会出现在任何位置)。
首先,请查看 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
传入两个 值 作为 a
和 b
,而不是索引。所以这段代码不起作用。
所有其他答案都基于 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));
一个不改变源数组的洗牌函数
更新:在这里我建议一个相对简单(不是从复杂性的角度来看)和简短的算法,它可以很好地处理小型数组,但是当你处理大型数组时,它肯定会比经典的 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;
}
splice
是一种非常低效的方式来执行他们所谓的“剔除”。如果您不想改变原始数组,则只需复制它,然后使用更有效的 Durstenfeld 变体将副本随机播放到位。
splice
方法创建一个副本,如下所示:source = array.slice();
。
使用 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']
您可以使用 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(改组);
您可以通过以下方式轻松完成:
// 数组 var fruits = ["Banana", "Orange", "Apple", "Mango"]; // random fruits.sort(function(a, b){return 0.5 - Math.random()}); // 输出 console.log(fruits);
我们在 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%}
对 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;
};
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;
}
随机化数组
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
,因为您使用的是 <
而不是 <=
不定期副业成功案例分享
i--
而不是--i
。此外,测试if (i==0)...
是多余的,因为如果i == 0
永远不会进入 while 循环。使用...| 0
可以更快地调用Math.floor
。 tempi 或 tempj 都可以被移除,并根据需要将值直接分配给 myArray[i] 或 j .for
循环,错误地将!=
与!==
一起使用,如果传递一个空数组,则无限循环,以及修改和返回一个范围。