【问题标题】:How to randomize (shuffle) a JavaScript array?如何随机化(随机播放)JavaScript 数组?
【发布时间】:2011-01-27 21:37:53
【问题描述】:

我有一个这样的数组:

var arr1 = ["a", "b", "c", "d"];

如何随机化/随机播放?

【问题讨论】:

  • 只要把它扔在这里,你就可以用 Mike Bostock 制作的这个可视化器来可视化 shuffle 函数的实际随机性:bost.ocks.org/mike/shuffle/compare.html
  • @Blazemonger jsPref 已死。你能在这里发布最快的吗?
  • 单线怎么样?返回的数组被打乱。 arr1.reduce((a,v)=>a.splice(Math.floor(Math.random() * a.length), 0, v) && a, [])
  • 这个怎么样? arr1.sort(() => (Math.random() > .5) ? 1 : -1);
  • @TheVee 看到上面几行,在同一规范上:“排序顺序是实现定义的,如果 ...如果 comparefn 不是未定义的并且不是项目元素的一致比较函数”

标签: javascript arrays random shuffle


【解决方案1】:

事实上的无偏洗牌算法是Fisher-Yates (aka Knuth) Shuffle

你可以看到great visualization here(和原帖linked to this

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);

使用了更多信息about the algorithm

【讨论】:

  • 以上答案跳过元素0,条件应该是i--而不是--i。此外,测试if (i==0)... 是多余的,因为如果i == 0 永远不会进入while 循环。使用...| 0 可以更快地调用Math.floortempitempj 都可以被移除,并根据需要将值直接分配给 myArray[i]j .
  • @RobG 上面的实现在功能上是正确的。在 Fisher-Yates 算法中,循环并不意味着针对数组中的第一个元素运行。查看wikipedia,其中还有其他实现也跳过了第一个元素。另请查看this 文章,该文章讨论了为什么不为第一个元素运行循环很重要。
  • 如果您要在繁忙的循环中进行解构分配,请务必转译——分配对象的成本很高。
  • @ggorlen 在这种情况下转译是什么意思?您能给我们举个例子或进一步解释吗?
  • @ggorlen 谢谢!我现在明白了——我很困惑,因为我通常使用 Node,所以没有立即想到浏览器。感谢您的澄清!
【解决方案2】:

就地洗牌

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]
    )
}

【讨论】:

  • 那么,ES6(ES2015) 在哪里? [array[i], array[rand]]=[array[rand], array[i]] ?也许你可以概述它是如何工作的。为什么选择向下迭代?
  • @sheriffderek 是的,我使用的 ES6 特性是一次分配两个变量,这允许我们在一行代码中交换两个变量。
  • 感谢@sheriffderek,他提出了升序算法。升序算法可以用归纳法证明。
【解决方案3】:

或者像上面所有的答案一样,但简而言之。

function shuffle(a) { for (var c, d, b = a.length; 0 !== b;)d = Math.floor(Math.random() * b), b -= 1, c = a[b], a[b] = a[d], a[d] = c; return a }

【讨论】:

    【解决方案4】:

    我找不到一个我喜欢的。这是我想出的解决方案。我没有使用太多无意义的变量,因为这是我现在的编码方式。

    Array.prototype.shuffle = function() {
        for (let i in this) {
            if (this.hasOwnProperty(i)) {
                let index = Math.floor(Math.random() * i);
                [
                    this[i],
                    this[index]
                ] = [
                    this[index],
                    this[i]
                ];
            }
        }
    
        return this;
    }
    

    let arrayA = [
        "item1", "item2", "item3", "item4", "item5"
    ];
    
    Array.prototype.shuffle = function() {
        for (let i in this) {
            if (this.hasOwnProperty(i)) {
                let index = Math.floor(Math.random() * i);
                [
                    this[i],
                    this[index]
                ] = [
                    this[index],
                    this[i]
                ];
            }
        }
        
        return this;
    }
    
    console.log(arrayA.shuffle());

    我希望这对那些可能不太了解这一点的人有所帮助。

    【讨论】:

      【解决方案5】:

      为了获得更大的灵活性,您可以添加另一个参数。在这种情况下,您可以从一个数组中取出一个随机数组并指定新数组的长度:

        function shuffle(array, len = array.length) {
              for (let i = array.length - 1; i > 0; i--) {
                  let j = Math.floor(Math.random() * (i + 1));
                  [array[i], array[j]] = [array[j], array[i]];
              }
      
              return array.slice(0, len);
          }
      

      【讨论】:

        【解决方案6】:

        我使用这两种方法:

        此方法不会修改原始数组

        shuffle(array);
        

        function shuffle(arr) {
            var len = arr.length;
            var d = len;
            var array = [];
            var k, i;
            for (i = 0; i < d; i++) {
                k = Math.floor(Math.random() * len);
                array.push(arr[k]);
                arr.splice(k, 1);
                len = arr.length;
            }
            for (i = 0; i < d; i++) {
                arr[i] = array[i];
            }
            return arr;
        }
        
        var arr = ["a", "b", "c", "d"];
        arr = shuffle(arr);
        console.log(arr);

        该方法修改原数组

        array.shuffle();
        

        Array.prototype.shuffle = function() {
            var len = this.length;
            var d = len;
            var array = [];
            var k, i;
            for (i = 0; i < d; i++) {
                k = Math.floor(Math.random() * len);
                array.push(this[k]);
                this.splice(k, 1);
                len = this.length;
            }
            for (i = 0; i < d; i++) {
                this[i] = array[i];
            }
        }
        
        var arr = ["a", "b", "c", "d"];
        arr.shuffle();
        console.log(arr);

        【讨论】:

          【解决方案7】:

          您可以使用 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)
          
          1. 我们将数组中的每个元素放入一个对象中,并给它一个随机排序键
          2. 我们使用随机键进行排序
          3. 我们取消映射以获取原始对象

          你可以打乱多态数组,排序和 Math.random 一样随机,这对于大多数用途来说已经足够了。

          由于元素是根据每次迭代都不会重新生成的一致键排序的,并且每次比较都来自相同的分布,因此 Math.random 分布中的任何非随机性都会被抵消。

          速度

          时间复杂度为 O(N log N),与快速排序相同。空间复杂度为 O(N)。这不如 Fischer Yates shuffle 高效,但在我看来,代码明显更短且功能更强大。如果你有一个大数组,你当然应该使用 Fischer Yates。如果你有一个包含几百个项目的小数组,你可以这样做。

          【讨论】:

          • 非常好。这是 js 中的Schwartzian transform
          • 这是最好的答案(对于短数组),原因有很多。对我来说,它真的很有用,因为我在 2021 年使用 react,它最适合像这样的函数式方法。
          【解决方案8】:

          我发现这很有用:

          const shuffle = (array: any[]) => {
              return array.slice().sort(() => Math.random() - 0.5);
            }
                  
          console.log(shuffle([1,2,3,4,5,6,7,8,9,10]));
          // Output: [4, 3, 8, 10, 1, 7, 9, 2, 6, 5]
          

          【讨论】:

          【解决方案9】:

          这里有简单的while循环

           function ShuffleColor(originalArray) {
                  let shuffeledNumbers = [];
                  while (shuffeledNumbers.length <= originalArray.length) {
                      for (let _ of originalArray) {
                          const randomNumb = Math.floor(Math.random() * originalArray.length);
                          if (!shuffeledNumbers.includes(originalArray[randomNumb])) {
                              shuffeledNumbers.push(originalArray[randomNumb]);
                          }
                      }
                      if (shuffeledNumbers.length === originalArray.length)
                          break;
                  }
                  return shuffeledNumbers;
              }
          const colors = [
              '#000000',
              '#2B8EAD',
              '#333333',
              '#6F98A8',
              '#BFBFBF',
              '#2F454E'
          ]
          ShuffleColor(colors)
          

          【讨论】:

            【解决方案10】:
             const arr = [
              { index: 0, value: "0" },
              { index: 1, value: "1" },
              { index: 2, value: "2" },
              { index: 3, value: "3" },
            ];
            let shuffle = (arr) => {
              let set = new Set();
              while (set.size != arr.length) {
                let rand = Math.floor(Math.random() * arr.length);
                set.add(arr[rand]);
              }
              console.log(set);
            };
            shuffle(arr);
            

            【讨论】:

              【解决方案11】:
              //doesn change array
              Array.prototype.shuffle = function () {
                  let res = [];
                  let copy = [...this];
              
                  while (copy.length > 0) {
                      let index = Math.floor(Math.random() * copy.length);
                      res.push(copy[index]);
                      copy.splice(index, 1);
                  }
              
                  return res;
              };
              
              let a=[1, 2, 3, 4, 5, 6, 7, 8, 9];
              console.log(a.shuffle());
              

              【讨论】:

                【解决方案12】:

                基准测试

                让我们先看看结果,然后我们将看看下面shuffle 的每个实现 -


                拼接速度很慢

                在循环中使用spliceshift 的任何解决方案都会非常缓慢。当我们增加数组的大小时,这一点尤其明显。在一个朴素的算法中,我们 -

                1. 在输入数组t中获取rand位置i
                2. 在输出中添加t[i]
                3. splice 位置 i 来自数组 t

                为了夸大缓慢的效果,我们将在包含一百万个元素的数组上进行演示。以下脚本差不多 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)
                
                function swap (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: "1 million elements via splice";
                  font-weight: bold;
                  display: block;
                }

                pop 很快

                诀窍不是splice,而是使用超级高效的pop。为此,您可以代替典型的 splice 调用 -

                1. 选择要拼接的位置,i
                2. t[i] 与最后一个元素t[t.length - 1] 交换
                3. t.pop() 添加到结果中

                现在我们可以在不到 100 毫秒shuffle 100 万个元素 -

                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)
                
                function swap (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: "1 million elements via pop";
                  font-weight: bold;
                  display: block;
                }

                更快

                上面shuffle 的两个实现产生了一个new 输出数组。输入数组未修改。这是我首选的工作方式,但是您可以通过原地改组来进一步提高速度。

                低于shuffle 100 万个元素,不到 10 毫秒 -

                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)
                
                function swap (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: "1 million elements in place";
                  font-weight: bold;
                  display: block;
                }

                【讨论】:

                  【解决方案13】:

                  我们在 2019 年仍然在洗牌,所以这是我的方法,这似乎很整洁,fast 对我来说:

                  const src = [...'abcdefg'];
                  
                  const shuffle = arr => 
                    [...arr].reduceRight((res,_,__,s) => 
                      (res.push(s.splice(0|Math.random()*s.length,1)[0]), res),[]);
                  
                  console.log(shuffle(src));
                  .as-console-wrapper {min-height: 100%}

                  【讨论】:

                    【解决方案14】:

                    警告!
                    使用该算法不推荐,因为它效率低有很强的偏见;见 cmets。它留在这里以备将来参考,因为这个想法并不罕见。

                    [1,2,3,4,5,6].sort( () => .5 - Math.random() );
                    

                    这个https://javascript.info/array-methods#shuffle-an-array 教程直截了当地解释了这些差异。

                    【讨论】:

                    • 投反对票,因为这并不是那么随机。我不知道为什么它有这么多的赞成票。不要使用这种方法。它看起来很漂亮,但并不完全正确。以下是 10,000 次迭代后数组中每个数字命中索引 [0] 的次数的结果(我也可以给出其他结果):1 = 29.19%,2 = 29.53%,3 = 20.06%,4 = 11.91%, 5 = 5.99%,6 = 3.32%
                    • 如果您需要随机化相对较小的数组并且不处理加密事物,这很好。我完全同意,如果您需要更多 随机性,您需要使用更复杂的解决方案。
                    • 问题是它不是确定性的,这会给出错误的结果(如果 1 > 2 和 2 > 3,应该给出 1 > 3,但这不能保证。这会混淆排序,并给出@radtad 评论的结果)。
                    【解决方案15】:
                    //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 是一个随机数,可以是正数或 负数,所以排序函数随机重新排序元素。

                    【讨论】:

                    【解决方案16】:

                    使用排序方法和数学方法:

                    var arr =  ["HORSE", "TIGER", "DOG", "CAT"];
                    function shuffleArray(arr){
                      return arr.sort( () => Math.floor(Math.random() * Math.floor(3)) - 1)  
                    }
                    
                    // every time it gives random sequence
                    shuffleArr(arr);
                    // ["DOG", "CAT", "TIGER", "HORSE"]
                    // ["HORSE", "TIGER", "CAT", "DOG"]
                    // ["TIGER", "HORSE", "CAT", "DOG"]
                    

                    【讨论】:

                    • 这不是随机的。在 sort() 中使用 random() 的类似答案请参见其他 cmets。
                    【解决方案17】:

                    我喜欢分享解决这个问题的百万种方法之一 =)

                    function shuffleArray(array = ["banana", "ovo", "salsicha", "goiaba", "chocolate"]) {
                    const newArray = [];
                    let number = Math.floor(Math.random() * array.length);
                    let count = 1;
                    newArray.push(array[number]);
                    
                    while (count < array.length) {
                        const newNumber = Math.floor(Math.random() * array.length);
                        if (!newArray.includes(array[newNumber])) {
                            count++;
                            number = newNumber;
                            newArray.push(array[number]);
                        }
                    }
                    
                    return newArray;
                    

                    }

                    【讨论】:

                    • 你试过用一百万个元素吗?
                    • 我希望这是O (n ^ 2)。这就是我问的原因。
                    • 我是为一个小收藏而制作的,所以我并不担心。我得到的收藏肯定是最多 20 件。好观察!
                    • 是的,总是有一个问题是何时需要进行任何优化。通常,在处理少量数据时,这很愚蠢。但是这里的几个答案已经发布了最常见的有效洗牌(Fischer-Yates)的变体,它们并不比这复杂多少。我并不是说这里有什么问题,只是你可能希望避免在大型数组中出现这种情况。
                    【解决方案18】:

                    使用Fisher-Yates shuffle 算法和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']
                    

                    【讨论】:

                    • 很棒且易于理解。
                    【解决方案19】:

                    这是最简单的一个,

                    function shuffle(array) {
                      return array.sort(() => Math.random() - 0.5);
                    }
                    

                    更多示例,您可以查看here

                    【讨论】:

                    【解决方案20】:

                    使用递归 JS 洗牌数组。

                    不是最好的实现,但它是递归的并且尊重不变性。

                    const randomizer = (array, output = []) => {
                        const arrayCopy = [...array];
                        if (arrayCopy.length > 0) {    
                            const idx = Math.floor(Math.random() * arrayCopy.length);
                            const select = arrayCopy.splice(idx, 1);
                            output.push(select[0]);
                            randomizer(arrayCopy, output);
                        }
                        return output;
                    };
                    

                    【讨论】:

                      【解决方案21】:

                      这是 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]];
                          }
                      }
                      

                      【讨论】:

                      【解决方案22】:

                      社区说 arr.sort((a, b) =&gt; 0.5 - Math.random()) 不是 100% 随机的!
                      是的!我测试并推荐不要使用这种方法!

                      let arr = [1, 2, 3, 4, 5, 6]
                      arr.sort((a, b) => 0.5 - Math.random());
                      

                      但我不确定。所以我写了一些代码来测试!...你也可以试试!如果你有足够的兴趣!

                      let data_base = []; 
                      for (let i = 1; i <= 100; i++) { // push 100 time new rendom arr to data_base!
                        data_base.push(
                          [1, 2, 3, 4, 5, 6].sort((a, b) => {
                            return  Math.random() - 0.5;     // used community banned method!  :-)      
                          })
                        );
                      } // console.log(data_base);  // if you want to see data!
                      let analysis = {};
                      for (let i = 1; i <= 6; i++) {
                        analysis[i] = Array(6).fill(0);
                      }
                      for (let num = 0; num < 6; num++) {
                        for (let i = 1; i <= 100; i++) {
                          let plus = data_base[i - 1][num];
                          analysis[`${num + 1}`][plus-1]++;
                        }
                      }
                      console.log(analysis); // analysed result 

                      在 100 个不同的随机数组中。 (我的分析结果)

                      { player> 1   2   3  4   5   6
                         '1': [ 36, 12, 17, 16, 9, 10 ],
                         '2': [ 15, 36, 12, 18, 7, 12 ],
                         '3': [ 11, 8, 22, 19, 17, 23 ],
                         '4': [ 9, 14, 19, 18, 22, 18 ],
                         '5': [ 12, 19, 15, 18, 23, 13 ],
                         '6': [ 17, 11, 15, 11, 22, 24 ]
                      }  
                      // player 1 got > 1(36 times),2(15 times),...,6(17 times)
                      // ... 
                      // ...
                      // player 6 got > 1(10 times),2(12 times),...,6(24 times)
                      

                      如您所见,它并没有那么随机!苏... 不要使用这种方法!


                      如果您进行多次测试。您会看到玩家 1 获得(编号 1)这么多次!
                      而玩家 6 获得(编号 6)大部分时间!

                      【讨论】:

                        【解决方案23】:

                        只是为了在馅饼中插上一根手指。在这里,我提出了 Fisher Yates shuffle 的递归实现(我认为)。它提供了均匀的随机性。

                        注意:~~(双波浪号运算符)实际上与正实数的 Math.floor() 类似。它只是一个捷径。

                        var shuffle = a => a.length ? a.splice(~~(Math.random()*a.length),1).concat(shuffle(a))
                                                    : a;
                        
                        console.log(JSON.stringify(shuffle([0,1,2,3,4,5,6,7,8,9])));

                        编辑:由于使用.splice(),上面的代码是O(n^2),但我们可以通过交换技巧消除O(n)中的拼接和洗牌。

                        var shuffle = (a, l = a.length, r = ~~(Math.random()*l)) => l ? ([a[r],a[l-1]] = [a[l-1],a[r]], shuffle(a, l-1))
                                                                                      : a;
                        
                        var arr = Array.from({length:3000}, (_,i) => i);
                        console.time("shuffle");
                        shuffle(arr);
                        console.timeEnd("shuffle");

                        问题是,JS 不能配合大递归。在这种特殊情况下,您的数组大小受限于 3000~7000,具体取决于您的浏览器引擎和一些未知事实。

                        【讨论】:

                          【解决方案24】:

                          编辑:这个答案不正确

                          请参阅 cmets 和 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);
                            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>

                          【讨论】:

                          • 不错,但每次都会生成一个完整的随机元素吗?
                          • 不太确定我是否理解正确。每次调用排序数组时,这种方法确实会以随机方式(尽管是伪随机)对数组进行洗牌 - 由于显而易见的原因,它不是一种稳定的排序。
                          • 出于与stackoverflow.com/a/18650169/28234 中解释的相同原因。这更有可能将早期元素留在数组的开头附近。
                          • 当您需要对数组进行加扰时,这是一个很棒的、简单的单行程序,但不要太在意让结果在学术上可证明是随机的。有时,最后几英寸的完美花费比它的价值更多的时间。
                          • 如果这行得通,那就太好了,但事实并非如此。由于快速搜索的工作方式,不一致的比较器可能会使数组元素靠近其原始位置。你的数组不会被打乱。
                          【解决方案25】:
                          [1, 2, 3, 4, 5, 6, 7, 8, 9, 0].sort((x, z) => {
                              ren = Math.random();
                              if (ren == 0.5) return 0;
                              return ren > 0.5 ? 1 : -1
                          })
                          

                          【讨论】:

                          • 这是公正的吗?
                          • 什么?这太没有意义了。它几乎有 0 机会保持元素完整(随机生成正好 0.5)
                          【解决方案26】:

                          重建整个数组,一个一个地把每个元素放在一个随机的地方。

                          [1,2,3].reduce((a,x,i)=>{a.splice(Math.floor(Math.random()*(i+1)),0,x);return a},[])
                          

                          var ia= [1,2,3];
                          var it= 1000;
                          var f = (a,x,i)=>{a.splice(Math.floor(Math.random()*(i+1)),0,x);return a};
                          var a = new Array(it).fill(ia).map(x=>x.reduce(f,[]));
                          var r = new Array(ia.length).fill(0).map((x,i)=>a.reduce((i2,x2)=>x2[i]+i2,0)/it)
                          
                          console.log("These values should be quite equal:",r);

                          【讨论】:

                          • 你应该解释一下你的代码在做什么,有些人可能不理解这种复杂性的 1 行。
                          • 另请注意,由于使用Math.round(... * i),这是有偏见的,您想改为使用Math.floor(.. * (i+1))
                          • @SamMason 获得 0.5 的概率为 1:1000000000000000000
                          • 如果使用round,则选择第一个和最后一个索引(即0n)的概率为0.5/n,选择任何其他元素的概率为1/n(其中n = a.length)。这对于短数组来说非常糟糕
                          • @SamMason 感谢您指出错误,我已经更新了答案并做了一个测试员
                          【解决方案27】:

                          有趣的是,没有不可变的递归答案:

                          var shuffle = arr => {
                            const recur = (arr,currentIndex)=>{
                              console.log("What?",JSON.stringify(arr))
                              if(currentIndex===0){
                                return arr;
                              }
                              const randomIndex = Math.floor(Math.random() * currentIndex);
                              const swap = arr[currentIndex];
                              arr[currentIndex] = arr[randomIndex];
                              arr[randomIndex] = swap;
                              return recur(
                                arr,
                                currentIndex - 1
                              );
                            }
                            return recur(arr.map(x=>x),arr.length-1);
                          };
                          
                          var arr = [1,2,3,4,5,[6]];
                          console.log(shuffle(arr));
                          console.log(arr);

                          【讨论】:

                          • 也许不是因为它的效率很低? :-P
                          • @Bergi 正确,更新了第一个答案逻辑。仍然需要复制数组以实现不变性。添加是因为这被标记为一个问题的副本,该问题要求一个函数接受一个数组并返回一个无序数组而不改变数组。现在这个问题实际上有了 OP 正在寻找的答案。
                          【解决方案28】:

                          随机推送或取消移位(在开头添加)。

                          ['a', 'b', 'c', 'd'].reduce((acc, el) => {
                            Math.random() > 0.5 ? acc.push(el) : acc.unshift(el);
                            return acc;
                          }, []);
                          

                          【讨论】:

                            【解决方案29】:
                            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;
                            };
                            

                            【讨论】:

                            • 这显然不如 Fisher-Yates 算法最优,但它适用于技术面试吗?
                            • @Andrea 由于在 for 循环中更改了数组长度,因此代码被破坏了。在最后一次编辑中,此问题已得到纠正。
                            • 你没有声明你的变量,这使得它们成为全局变量——而且这个函数似乎从输入数组中随机删除元素。
                            【解决方案30】:

                            虽然已经建议了许多实现,但我觉得我们可以使用 forEach 循环使其更短更容易,因此我们无需担心计算数组长度,而且我们可以安全地避免使用临时变量。

                            var myArr = ["a", "b", "c", "d"];
                            
                            myArr.forEach((val, key) => {
                              randomIndex = Math.ceil(Math.random()*(key + 1));
                              myArr[key] = myArr[randomIndex];
                              myArr[randomIndex] = val;
                            });
                            // see the values
                            console.log('Shuffled Array: ', myArr)
                            

                            【讨论】:

                              猜你喜欢
                              • 2019-04-03
                              • 1970-01-01
                              • 2023-01-23
                              • 2020-12-10
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多