如何随机化(洗牌)JavaScript 数组?
我有一个像这样的数组:
var arr1 = ["a", "b", "c", "d"];
如何随机/洗牌它?
I have an array like this:
var arr1 = ["a", "b", "c", "d"];
How can I randomize / shuffle it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
事实上的无偏洗牌算法是Fisher–Yates(又名 Knuth)洗牌。
您可以在此处看到精彩的可视化。
The de-facto unbiased shuffle algorithm is the Fisher–Yates (aka Knuth) Shuffle.
You can see a great visualization here.
下面是 Durstenfeld shuffle 的 JavaScript 实现,它是 Fisher-Yates 的优化版本:
它为每个原始数组元素选择一个随机元素,并将其从下一次抽奖中排除,就像从一副牌中随机选择一样。
这种巧妙的排除将选取的元素与当前元素交换,然后从剩余元素中选取下一个随机元素,向后循环以获得最佳效率,确保随机选取得到简化(它始终可以从 0 开始),从而跳过最后一个元素。
算法运行时间为
O(n)
。 请注意,随机播放是就地完成的,因此如果您不想修改原始数组,请首先使用.slice(0)
。编辑:更新到 ES6 / ECMAScript 2015
新的 ES6 允许我们同时分配两个变量。当我们想要交换两个变量的值时,这特别方便,因为我们可以在一行代码中完成。这是使用此功能的同一函数的较短形式。
Here's a JavaScript implementation of the Durstenfeld shuffle, an optimized version of Fisher-Yates:
It picks a random element for each original array element, and excludes it from the next draw, like picking randomly from a deck of cards.
This clever exclusion swaps the picked element with the current one, then picks the next random element from the remainder, looping backwards for optimal efficiency, ensuring the random pick is simplified (it can always start at 0), and thereby skipping the final element.
Algorithm runtime is
O(n)
. Note that the shuffle is done in-place so if you don't want to modify the original array, first make a copy of it with.slice(0)
.EDIT: Updating to ES6 / ECMAScript 2015
The new ES6 allows us to assign two variables at once. This is especially handy when we want to swap the values of two variables, as we can do it in one line of code. Here is a shorter form of the same function, using this feature.
您可以使用地图和排序轻松完成:
您可以对多态数组进行打乱,并且排序与 Math.random 一样随机,即对于大多数用途来说已经足够了。
由于元素是根据每次迭代不会重新生成的一致键进行排序的,并且每次比较都从相同的分布中提取,因此 Math.random 分布中的任何非随机性都会被抵消。
速度
时间复杂度为O(N log N),与快速排序相同。空间复杂度为 O(N)。这不像 Fischer Yates shuffle 那样高效,但在我看来,代码明显更短且更实用。如果您有一个大型数组,您当然应该使用 Fischer Yates。如果您有一个包含数百个项目的小数组,您可以这样做。
You can do it easily with map and sort:
You can shuffle polymorphic arrays, and the sort is as random as Math.random, which is good enough for most purposes.
Since the elements are sorted against consistent keys that are not regenerated each iteration, and each comparison pulls from the same distribution, any non-randomness in the distribution of Math.random is canceled out.
Speed
Time complexity is O(N log N), same as quick sort. Space complexity is O(N). This is not as efficient as a Fischer Yates shuffle but, in my opinion, the code is significantly shorter and more functional. If you have a large array you should certainly use Fischer Yates. If you have a small array with a few hundred items, you might do this.
这个https://javascript.info/array-methods#shuffle-an-array 教程简单地解释了差异。
This https://javascript.info/array-methods#shuffle-an-array tutorial explains the differences straightforwardly.
使用 underscore.js 库。方法
_.shuffle()
非常适合这种情况。这是该方法的示例:
Use the underscore.js library. The method
_.shuffle()
is nice for this case.Here is an example with the method:
新的!
更短和更短可能*更快的 Fisher-Yates 洗牌算法
脚本大小(以 fy 作为函数名称):90bytes
DEMO
http://jsfiddle.net/vvpoma8w/
*可能在除 Chrome 之外的所有浏览器上速度更快。
如果您有任何问题,请尽管提问。
编辑
是的,它更快
性能:http://jsperf.com/ fyshuffle
使用投票最多的函数。
编辑
计算量过多(不需要--c+1)并且没有人注意到
更短(4字节)和更快(测试一下!)。
在其他地方缓存
var rnd=Math.random
然后使用rnd()
也会稍微提高大数组的性能。http://jsfiddle.net/vvpoma8w/2/
可读版本 (使用原始版本。这比较慢,变量没有用,比如闭包和“;”,代码本身也更短......也许阅读这个如何“缩小”Javascript 代码,顺便说一句,您无法在像上面那样的 javascript 缩小器中压缩以下代码。)
NEW!
Shorter & probably *faster Fisher-Yates shuffle algorithm
script size (with fy as function name): 90bytes
DEMO
http://jsfiddle.net/vvpoma8w/
*faster probably on all browsers except chrome.
If you have any questions just ask.
EDIT
yes it is faster
PERFORMANCE: http://jsperf.com/fyshuffle
using the top voted functions.
EDIT
There was a calculation in excess (don't need --c+1) and noone noticed
shorter(4bytes)&faster(test it!).
Caching somewhere else
var rnd=Math.random
and then usernd()
would also increase slightly the performance on big arrays.http://jsfiddle.net/vvpoma8w/2/
Readable version (use the original version. this is slower, vars are useless, like the closures & ";", the code itself is also shorter ... maybe read this How to 'minify' Javascript code , btw you are not able to compress the following code in a javascript minifiers like the above one.)
随机排列数组
ES6 纯迭代
可靠性和性能测试
此页面上的一些解决方案并不可靠(它们仅部分随机化数组) 。其他解决方案的效率明显较低。使用 testShuffleArrayFun(见下文),我们可以测试数组混洗函数的可靠性和性能。
其他解决方案
其他解决方案只是为了好玩。
ES6 Pure,递归
ES6 Pure 使用 array.map
ES6 Pure 使用 array.reduce
Shuffle Array In place
ES6 Pure, Iterative
Reliability and Performance Test
Some solutions on this page aren't reliable (they only partially randomise the array). Other solutions are significantly less efficient. With
testShuffleArrayFun
(see below) we can test array shuffling functions for reliability and performance.Other Solutions
Other solutions just for fun.
ES6 Pure, Recursive
ES6 Pure using array.map
ES6 Pure using array.reduce
编辑:这个答案不正确
请参阅评论和https://stackoverflow.com/a/18650169/28234< /a>.它被留在这里供参考,因为这个想法并不罕见。
对于小型数组来说,一个非常简单的方法就是这样:
它可能不是很有效,但对于小型数组来说,这工作得很好。这是一个示例,您可以了解它的随机性(或不随机性),以及它是否适合您的用例。
Edit: This answer is incorrect
See comments and https://stackoverflow.com/a/18650169/28234. It is being left here for reference because the idea isn't rare.
A very simple way for small arrays is simply this:
It's probably not very efficient, but for small arrays this works just fine. Here's an example so you can see how random (or not) it is, and whether it fits your usecase or not.
基准
首先让我们看看结果,然后我们将看看下面
shuffle
的每个实现 -拼接速度慢
使用
splice
或的任何解决方案循环中的 shift
会非常慢。当我们增加数组的大小时,这一点尤其明显。在朴素算法中,我们rand
位置i
,t
t[i] 到数组
t
中的输出splice
位置i
为了夸大缓慢的效果,我们将在包含一百万个元素的数组上演示这一点。以下脚本近 30 秒 -
pop 很快
诀窍是不要
拼接
,而是使用超高效的pop
。为此,您可以splice
调用,i
t[i]
元素,t[t.length - 1]
t.pop()
添加到结果中现在我们可以
随机排列
中的一百万个元素>小于 100 毫秒 -甚至更快
上面的两个
shuffle
实现会生成一个新输出数组。输入数组未修改。这是我首选的工作方式,但是您可以通过原地洗牌来进一步提高速度。下面
随机播放
一百万个元素,不到 10 毫秒 -benchmarks
Let's first see the results then we'll look at each implementation of
shuffle
below -splice is slow
Any solution using
splice
orshift
in a loop is going to be very slow. Which is especially noticeable when we increase the size of the array. In a naive algorithm we -rand
position,i
, in the input array,t
t[i]
to the outputsplice
positioni
from arrayt
To exaggerate the slow effect, we'll demonstrate this on an array of one million elements. The following script almost 30 seconds -
pop is fast
The trick is not to
splice
and instead use the super efficientpop
. To do this, in place of the typicalsplice
call, you -i
t[i]
with the last element,t[t.length - 1]
t.pop()
to the resultNow we can
shuffle
one million elements in less than 100 milliseconds -even faster
The two implementations of
shuffle
above produce a new output array. The input array is not modified. This is my preferred way of working however you can increase the speed even more by shuffling in place.Below
shuffle
one million elements in less than 10 milliseconds -您可以使用
Math.random
任意决定是否返回1 : -1
:尝试运行以下示例:
You can arbitrarily decide whether to return
1 : -1
by usingMath.random
:Try running the following example:
我发现这个变体挂在这个问题的副本的“作者删除”答案中。与已经有很多赞成票的其他一些答案不同,这是:
shuffled
名称而不是shuffle
)这是一个 jsfiddle 显示其使用情况。
I found this variant hanging out in the "deleted by author" answers on a duplicate of this question. Unlike some of the other answers that have many upvotes already, this is:
shuffled
name rather thanshuffle
)Here's a jsfiddle showing it in use.
在 ES2015 中,你可以使用这个:
用法:
With ES2015 you can use this one:
Usage:
https://javascript.info/task/shuffle
https://javascript.info/task/shuffle
递归解决方案:
A recursive solution:
使用 ES6 功能的现代短内联解决方案:
(用于教育目的)
Modern short inline solution using ES6 features:
(for educational purposes)
Fisher-Yates 在 JavaScript 中随机播放。我将其发布在这里是因为与此处的其他答案相比,使用两个实用函数(swap 和 randInt)澄清了该算法。
Fisher-Yates shuffle in javascript. I'm posting this here because the use of two utility functions (swap and randInt) clarifies the algorithm compared to the other answers here.
使用 Fisher-Yates 洗牌算法和 ES6:
Using Fisher-Yates shuffle algorithm and ES6:
所有其他答案都基于 Math.random() ,它速度快但不适合加密级别随机化。
以下代码使用众所周知的
Fisher-Yates
算法,同时利用Web Cryptography API
实现加密级别的随机化。All the other answers are based on Math.random() which is fast but not suitable for cryptgraphic level randomization.
The below code is using the well known
Fisher-Yates
algorithm while utilizingWeb Cryptography API
for cryptographic level of randomization.不更改源数组的随机播放函数
免责声明
请注意,此解决方案不适合大型数组!如果您正在整理大型数据集,则应使用上面建议的 Durstenfeld 算法。
解决方案
工作原理
将初始
数组
复制到itemsLeft
从
itemsLeft
中选取随机索引,将相应的元素添加到结果 数组并将其从
itemsLeft
数组中删除repeats 步骤 (2) 直到
itemsLeft
数组变为空返回
结果
a shuffle function that doesn't change the source array
Disclaimer
Please note that this solution is not suitable for large arrays! If you are shuffling large datasets, you should use the Durstenfeld algorithm suggested above.
Solution
How it works
copies the initial
array
intoitemsLeft
picks up a random index from
itemsLeft
, adds the corresponding element to theresult
array and deletes it from theitemsLeft
arrayrepeats step (2) until
itemsLeft
array gets emptyreturns
result
首先,请查看此处,了解不同排序方法的直观比较在 JavaScript 中。
其次,如果您快速浏览一下上面的链接,您会发现随机顺序排序与其他方法相比似乎表现相对较好,同时实现起来非常容易和快速,如下所示:
编辑:正如@gregers所指出的,比较函数是使用值而不是索引来调用的,这就是您需要使用
indexOf
的原因。请注意,此更改使代码不太适合较大的数组,因为indexOf
的运行时间为 O(n) 时间。First of all, have a look here for a great visual comparison of different sorting methods in javascript.
Secondly, if you have a quick look at the link above you'll find that the
random order
sort seems to perform relatively well compared to the other methods, while being extremely easy and fast to implement as shown below:Edit: as pointed out by @gregers, the compare function is called with values rather than indices, which is why you need to use
indexOf
. Note that this change makes the code less suitable for larger arrays asindexOf
runs in O(n) time.对CoolAJ86的答案进行简单修改,不修改原始数组:
A simple modification of CoolAJ86's answer that does not modify the original array:
对于我们这些不太有天赋但能够接触到 lodash 奇迹的人来说,有这样的东西 lodash.shuffle。
For those of us who are not very gifted but have access to the wonders of lodash, there is such a thing as lodash.shuffle.
2019 年我们仍在对数组进行洗牌,所以这是我的方法,这似乎很简洁且快速 对我来说:
We're still shuffling arrays in 2019, so here goes my approach, which seems to be neat and fast to me:
您可以使用
lodash
随机播放。就像魅力一样工作You can use
lodash
shuffle. Works like a charm使用生成器函数的 ES6 紧凑代码*
它的工作原理是从未洗牌的数组的副本中随机删除项目,直到没有剩余的为止。它使用新的 ES6 生成器 函数。
只要 Math.random() 是公平的,这将是一个完全公平的洗牌。
或者,使用 ES6 和 splice:
或者,ES6索引交换方法:
ES6 compact code using generator function*
This works by randomly removing items from a copy of the unshuffled array until there are none left. It uses the new ES6 generator function.
This will be a perfectly fair shuffle as long as Math.random() is fair.
Alternatively, using ES6 and splice:
or, ES6 index swap method:
随机化数组
Randomize array
虽然已经建议了许多实现,但我觉得我们可以使用 forEach 循环使其更短、更容易,因此我们不需要担心计算数组长度,而且我们可以安全地避免使用临时变量。
Though there are a number of implementations already advised but I feel we can make it shorter and easier using forEach loop, so we don't need to worry about calculating array length and also we can safely avoid using a temporary variable.
最短的 arrayShuffle 函数
the shortest
arrayShuffle
function有趣的是,没有非变异的递归答案:
Funny enough there was no non mutating recursive answer: