我有一个数组,其形式如下:
var arr1 = ["a", "b", "c", "d"];
我该如何对它进行随机排列 / 洗牌?
我有一个数组,其形式如下:
var arr1 = ["a", "b", "c", "d"];
我该如何对它进行随机排列 / 洗牌?
事实上无偏的洗牌算法是 费雪-耶茨 (又名 Knuth) 洗牌算法。
你可以在这里看到一个 很棒的可视化效果(以及原始帖子链接到此处)
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex > 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
// Used like so
var arr = [2, 11, 37, 42];
shuffle(arr);
console.log(arr);
关于所使用算法的一些更多信息请点击这里。
i--
而不是 --i
。同时,测试语句 if (i==0)...
是多余的,因为如果 i == 0
,while 循环将永远不会被进入。使用 ...| 0
可以更快地执行调用到 Math.floor
。可以将 tempi 或 tempj 中的一个移除,并将值直接赋给适当的 myArray[i] 或 j。 - RobGfor
循环、错误地将 !=
与 !==
混淆使用、如果传入一个空数组则会进入无限循环、修改并返回参数。 - Sam/* 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允许我们同时赋值两个变量。当我们想要交换两个变量的值时,这尤其方便,因为我们可以在一行代码中完成它。以下是使用此功能的相同函数的较短形式。
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()
不应该乘以循环计数器加1,而是应该乘以 array.length()
。请参阅 在 JavaScript 中生成特定范围内的随机整数? 获取非常全面的解释。 - Marjan Venemareturn array;
了? - IRvanFauziEreturn array
只会使其更容易地与其他.链式调用。 - AgainPsychoX你可以很容易地使用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)
console.log(shuffled)
您可以洗牌多态数组,排序与Math.random一样随机,对于大多数目的来说足够好。
由于元素针对不在每次迭代中重新生成的一致键进行排序,并且每个比较从相同的分布中拉取,因此 Math.random 分布中的任何非随机性被抵消。
速度
时间复杂度为O(N log N),与快速排序相同。空间复杂度为O(N)。这不如Fischer Yates shuffle高效,但在我看来,代码明显更短且更实用。如果您有一个大型数组,应该使用Fischer Yates。如果您有一个只有几百个项目的小数组,可以使用此方法。
.sort
算法的快速排序复杂性。 - Ilja KO警告!
不建议使用此算法,因为它效率低下且严重偏差;请参阅评论。将其保留在这里以供参考是因为其思想并不罕见。
[1,2,3,4,5,6].sort( () => .5 - Math.random() );
这个https://javascript.info/array-methods#shuffle-an-array教程直观地解释了差异。
使用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();
有人可以(但不应该)将其用作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;
}
新!
更短、可能更快的费雪耶茨洗牌算法
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/
可读版本(使用原始版本。这种方式较慢,变量无用,像闭包和“;”一样,代码本身也更短...也许阅读此如何“缩小”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
}
}
function fy(a,b,c=a.length){while(c)b=Math.random()*(--c+1)|0,[a[c],a[b]]=[a[b],a[c]]}
- undefined原地打乱数组
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))];
};
使用数组 map 方法的 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]
})
}
使用数组reduce的ES6纯函数
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]]
?也许您可以概述一下它的工作原理。为什么选择向下迭代? - sheriffderek编辑:这个答案是不正确的。
请查看评论和https://dev59.com/IXE95IYBdhLWcg3wHqMV#18650169。这里保留此回答供参考,因为这个想法并不罕见。
对于小数组来说,一个非常简单的方法就是:
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>
警告!
不建议将此答案用于随机化大型数组、密码学或任何需要真正随机性的应用程序,因为它具有偏差和低效率。元素位置只是半随机化的,并且它们倾向于保持接近其原始位置。请参见https://dev59.com/IXE95IYBdhLWcg3wHqMV#18650169。
您可以使用Math.random
来任意决定是否返回1:-1
:
[1, 2, 3, 4].sort(() => (Math.random() > 0.5) ? 1 : -1)
const array = [1, 2, 3, 4];
// Based on the value returned by Math.Random,
// the decision is arbitrarily made whether to return 1 : -1
const shuffeled = array.sort(() => {
const randomTrueOrFalse = Math.random() > 0.5;
return randomTrueOrFalse ? 1 : -1
});
console.log(shuffeled);
a.sort(() => Math.random() - 0.5)
的意思是将数组 a 随机排序。 - SaboSuke