优雅地改组Javascript数组

时间:2017-01-08 18:55:59

标签: javascript sorting shuffle

有一些关于改组JS数组的现有线程,但它们看起来并不简单,也不优雅,由许多代码行组成。

我遇到了一些建议以下一行“解决方法”的博客:

yourArray.sort(function() { return 0.5 - Math.random() });

如果您不熟悉排序算法的工作原理,我将在此简要解释一下,它总是要比较数组的两个值,然后做出决定。建议的解决方案“覆盖”默认比较函数与另一个函数,对于两个单元格的每次比较,生成一个随机值(在每次比较时,它随机决定哪个大于哪个)。

问题在于,我无法知道每个Web浏览器使用哪种排序算法。对于某些排序算法,例如 BubbleSort ,这样的功能可能会导致排序算法永远运行,因为它具有 1/2 ^(长度)概率,可以在没有任何交换的情况下运行。对于 Quicksort 来说似乎也存在问题。我认为只有在网络浏览器使用 MergeSort HeapSort

时才会定期结束

有没有人尝试过,可以判断它是安全还是建议使用其他解决方案?

2 个答案:

答案 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对其进行了测试,您可以看到运行它们的结果:

&#13;
&#13;
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;
&#13;
&#13;