有一些关于改组JS数组的现有线程,但它们看起来并不简单,也不优雅,由许多代码行组成。
我遇到了一些建议以下一行“解决方法”的博客:
yourArray.sort(function() { return 0.5 - Math.random() });
如果您不熟悉排序算法的工作原理,我将在此简要解释一下,它总是要比较数组的两个值,然后做出决定。建议的解决方案“覆盖”默认比较函数与另一个函数,对于两个单元格的每次比较,生成一个随机值(在每次比较时,它随机决定哪个大于哪个)。
问题在于,我无法知道每个Web浏览器使用哪种排序算法。对于某些排序算法,例如 BubbleSort ,这样的功能可能会导致排序算法永远运行,因为它具有 1/2 ^(长度)概率,可以在没有任何交换的情况下运行。对于 Quicksort 来说似乎也存在问题。我认为只有在网络浏览器使用 MergeSort 或 HeapSort
时才会定期结束有没有人尝试过,可以判断它是安全还是建议使用其他解决方案?
答案 0 :(得分:3)
"解决方法"是个糟糕的主意。这是效率低下的(使用了比Math.random()
更多的调用),正如您所指出的,使用某些排序算法是危险的。它也有偏见(至少对于我的nodeJS安装中sort()
的版本,虽然我无法解释原因)。这是对数组[1, 2, 3]
进行60,000次排序并计算每个排列显示的数量的典型结果:
[1,2,3]:14,821次
[1,3,2]:7,637次
[2,1,3]:15,097倍
[2,3,1]:7,590次
[3,1,2]:7,416次
[3,2,1]:7,439次
对于无偏见的随机播放,六种排列应平均出现频率(重复60,000次约10,000次)。在我的实验中,[1,2,3]和[2,1,3]的出现大约是其他四种排列的两倍。这在许多测试中都是一致的。
你更好地坚持标准的Fisher-Yates shuffle(在this Wikipedia article和网络上的许多其他地方描述)。在伪代码中,它看起来像这样(取自维基百科的文章):
-- To shuffle an array a of n elements (indices 0..n-1):
for i from n−1 downto 1 do
j ← random integer such that 0 ≤ j ≤ i
exchange a[j] and a[i]
不太复杂,绝对是一种更好的方法。这是我的JavaScript版本(可能会稍微清理一下):
function shuffleFisherYates(a) {
var i, j, tmp;
for (i = a.length - 1; i > 0; --i) {
j = Math.floor(Math.random() * (i + 1));
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
return a;
}
P.S。作为参考,这里是我用来测试你的解决方法的代码:
function shuffleRandomSort(a) {
a.sort(function() { return 0.5 - Math.random() });
return a;
}
function score(scores, result) {
var index;
if (result[0] === 1) {
index = result[1] === 2
? 0 // [1, 2, 3]
: 1; // [1, 3, 2]
} else if (result[0] === 2) {
index = result[1] === 1
? 2 // [2, 1, 3]
: 3; // [2, 3, 1]
} else { // result[0] === 3
index = result[1] === 1
? 4 // [3, 1, 2]
: 5; // [3, 2, 1]
}
scores[index]++;
}
function runTest(shuffler, n) {
var scores = [0, 0, 0, 0, 0, 0],
a;
for (var i = 0; i < n; ++i) {
a = [1, 2, 3];
score(scores, shuffler(a));
}
console.log(scores);
}
console.log(shuffleRandomSort, runTest(60000));
答案 1 :(得分:1)
我抓住了一些算法并使用console.time
对其进行了测试,您可以看到运行它们的结果:
var smallArray = Array.from({ length: 10 }, (x, i) => i);
var bigArray = Array.from({ length: 1000 }, (x, i) => i);
function shuffle1(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (currentIndex) {
randomIndex = (Math.random() * currentIndex) | 0;
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
}
function shuffle2(arr) {
return _.shuffle(arr);
}
function shuffle3(arr) {
for (let i = arr.length - 1; i >= 0; --i) {
let j = (Math.random() * i) | 0;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// inconsistent speeds
function shuffle4(arr) {
arr.sort(function() { return 0.5 - Math.random() });
}
function test(label, fn) {
console.time(label);
for (let i = 0; i < 1000; ++i) {
fn();
}
console.timeEnd(label);
}
// time in comments based on Chrome 55
let sa1 = smallArray.slice(0);
let sa2 = smallArray.slice(0);
let sa3 = smallArray.slice(0);
let sa4 = smallArray.slice(0);
test('smallArray shuffle1', () => shuffle1(sa1)); // 0.785ms
test('smallArray shuffle2', () => shuffle2(sa2)); // 1.830ms
test('smallArray shuffle3', () => shuffle3(sa3)); // 5.540ms
test('smallArray shuffle4', () => shuffle4(sa4)); // 3.995ms
let ba1 = bigArray.slice(0);
let ba2 = bigArray.slice(0);
let ba3 = bigArray.slice(0);
let ba4 = bigArray.slice(0);
test('bigArray shuffle1', () => shuffle1(ba1)); // 14.195ms
test('bigArray shuffle2', () => shuffle2(ba2)); // 24.645ms
test('bigArray shuffle3', () => shuffle3(ba3)); // 119.425ms
test('bigArray shuffle4', () => shuffle4(ba4)); // 249.930ms
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
&#13;