我有一个这样的数组:
var arr1 = ["a", "b", "c", "d"];
我如何随机化/打乱它?
我有一个这样的数组:
var arr1 = ["a", "b", "c", "d"];
我如何随机化/打乱它?
当前回答
考虑将其应用于本地或新的不可变数组,遵循其他解决方案,以下是建议的实现:
Array.prototype.shuffle = function(local){
var a = this;
var newArray = typeof local === "boolean" && local ? this : [];
for (var i = 0, newIdx, curr, next; i < a.length; i++){
newIdx = Math.floor(Math.random()*i);
curr = a[i];
next = a[newIdx];
newArray[i] = next;
newArray[newIdx] = curr;
}
return newArray;
};
其他回答
随机化数组,无重复项
function randomize(array){
let nums = [];
for(let i = 0; i < array.length; ++i){
nums.push(i);
}
nums.sort(() => Math.random() - Math.random()).slice(0, array.length)
for(let i = 0; i < array.length; ++i){
array[i] = array[nums[i]];
}
}
randomize(array);
无序排列到位
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纯使用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纯使用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]
)
}
事实上的无偏洗牌算法是Fisher Yates(又名Knuth)shuffle。
你可以在这里看到一个很棒的可视化效果(以及链接到此的原始帖子)
函数洗牌(数组){let currentIndex=array.length,randomIndex;//而还有一些元素需要洗牌。while(currentIndex!=0){//拾取剩余的元素。randomIndex=数学地板(Math.random()*当前索引);当前索引--;//并将其与当前元素交换。[array[currentIndex],array[randomIndex]]=[array[randomIndex],array[currentIndex]];}返回数组;}//如此使用var arr=[2,11,37,42];洗牌(arr);控制台日志(arr);
有关所用算法的更多信息。
Array.prototype.shuffle=function(){
var len = this.length,temp,i
while(len){
i=Math.random()*len-- |0;
temp=this[len],this[len]=this[i],this[i]=temp;
}
return this;
}
从理论的角度来看,在我看来,最优雅的方法是得到一个介于0和n之间的随机数-并计算从{0,1,…,n!-1}到(0,1、2,…,n-1)的所有置换的一对一映射。只要你能使用一个足够可靠的(伪)随机发生器来获得这样一个数字而没有任何明显的偏差,你就有足够的信息来实现你想要的,而不需要其他几个随机数。
当使用IEEE754双精度浮点数计算时,您可以期望随机生成器提供大约15个小数。既然你有15岁=1307674368000(带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)的n阶排列:
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个元素的数组,您可以放心地认为绝对没有偏差。