我一直在用Random (java.util.Random)来洗牌52张牌。有52个!(8.0658175E+67)可能性。然而,我已经发现,java.util.Random的种子是long,比2^64(1.8446744e+19)小得多。
从这里,我怀疑java.util.Random是否真的是那么随机;它是否真的能够生成全部52个呢?可能性?
如果不是,我怎么能可靠地生成一个更好的随机序列,可以生成全部52个!可能性?
- "我怎么能生成一个超过52的真正随机数呢?"来自Random的数字绝不是实数随机数。这是一个prng,其中p代表"伪"。对于真正的随机数,您需要随机性的来源(如random.org)。
- 你是如何洗牌的?(Collections.shuffle或其他什么东西?)
- @吉米加里森,这不是行动的目的。他说的是10^68个可能的序列。由于每一个伪随机序列都是由它的种子来识别的,所以op说最多有2^64个不同的序列。
- @RadioDef-是的,确切地说,我到目前为止
- OP:我对你的问题进行了大量的修改,但我相信它准确地反映了你所关心的问题和你想解决的问题。如果我误解了,请随时重新编辑或回滚。
- @是的,这正是我要说的,谢谢!
- 我认为这是一个有趣的问题,值得思考。但我禁不住想知道你的问题背景:到底是什么导致了能够生成全部52个的需求!排列?例如,在现实世界的桥牌中,我们可以一次洗牌和发一张牌,但由于许多不同的排列导致同一只手,所以只有~6e11个不同的手。从另一个角度考虑,你是否需要一个专门针对52岁的人的解决方案?或者你需要一个概括的,比如说,两个牌叠在一起的(104!/(2**52)可能性,或~2E150)?
- @以纸牌(克朗代克)为例,52!就是可能的手的数量。
- @塞尔贾多维奇:太棒了,谢谢你举的例子!
- 你不能为每张卡选择一个随机位置而不是一次生成一个随机序列吗?也就是说,为第一张卡选择一个随机位置(1..52),然后为第二张卡选择一个随机位置(1..52减去第一张卡的位置),依此类推。
- @产生序列的害羞。(1/52,1/51,1/50,…,1/1.)这也是OP已经在做的。看到他们回复我的评论,确认他们使用了费希尔·耶茨洗牌的Collections.shuffle。
- 注意,阶乘的增长速度比指数的增长速度要快得多。这意味着,如果您增加足够的卡数,即使使用SecureRandom和byte[],也不会起作用。当然,你可以增加种子的大小,但不幸的是,你需要渐进地增加种子的大小,以便能够继续生成所有可能的排列,因此,即使对于"相对较小的n个数",你也不能生成所有n张卡片的排列,即使使用所有的me在地球上的储藏室。
- 我觉得这是一本有趣的书:superuser.com/a/712583
- 不,Java随机是不安全的,也不是随机的。SecureRandom有点随机,但我会更信任BouncyCastle随机生成器或OpenSSH。
- 顺便说一句,你为什么要它生成全部52个呢?可能性?我想从用户的角度来看,最重要的是它是不可预测的,这是完全不同的。毕竟,这场比赛只需列举所有52个!可能性,而且非常可预测…
选择一个随机排列比你的问题意味着更多和更少的随机性。让我解释一下。
坏消息是:需要更多的随机性。
你的方法的根本缺陷是它试图使用64位熵(随机种子)在~2226种可能性之间进行选择。要在~2226种可能性之间做出公平的选择,你必须找到一种方法来产生226位的熵,而不是64位的熵。
有几种生成随机位的方法:专用硬件、CPU指令、操作系统接口、在线服务。在你的问题中已经有了一个隐含的假设,你可以以某种方式产生64比特,所以只要做你想做的任何事情,只做四次,然后把多余的比特捐给慈善机构。:)
好消息是:需要更少的随机性。
一旦你有了226个随机位,剩下的就可以确定地完成,因此可以使java.util.Random的属性不相关。以下是方法。
假设我们生成全部52个!排列(请听我说)并按字典顺序排序。
要选择一个排列,我们只需要在0和52!-1之间选择一个随机整数。这个整数就是我们的226位熵。我们将使用它作为排序排列列表的索引。如果随机指数是均匀分布的,那么不仅保证所有排列都能被选择,而且它们也将被等量地选择(这比问题所问的问题更有力地保证)。
现在,您实际上不需要生成所有这些排列。您可以直接生成一个,考虑到它在我们的假设排序列表中随机选择的位置。这可以使用Lehmer[1]代码在O(n2)时间内完成(另请参见编号排列和阶乘编号系统)。这里的n是你甲板的大小,即52。
在这个stackoverflow答案中有一个C实现。对于N=52,有几个整数变量会溢出,但幸运的是,在爪哇中,可以使用EDCOX1×3。其余的计算几乎可以转录为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public static int[] shuffle (int n, BigInteger random_index ) {
int[] perm = new int[n ];
BigInteger[] fact = new BigInteger[n ];
fact [0] = BigInteger. ONE;
for (int k = 1; k < n ; ++k ) {
fact [k ] = fact [k - 1]. multiply(BigInteger. valueOf(k ));
}
// compute factorial code
for (int k = 0; k < n ; ++k ) {
BigInteger[] divmod = random_index. divideAndRemainder(fact [n - 1 - k ]);
perm [k ] = divmod [0]. intValue();
random_index = divmod [1];
}
// readjust values to obtain the permutation
// start from the end and check if preceding values are lower
for (int k = n - 1; k > 0; --k ) {
for (int j = k - 1; j >= 0; --j ) {
if (perm [j ] <= perm [k ]) {
perm [k ]++;
}
}
}
return perm ;
}
public static void main (String[] args ) {
System. out. printf("%s
", Arrays. toString(
shuffle (52, new BigInteger(
"7890123456789012345678901234567890123456789012345678901234567890"))));
} |
[1]不要与莱勒混淆。:)
- 呵呵,我相信最后的链接会是新的数学。-)
- @克劳德:几乎是!正是无限可微黎曼流形使它摆动。-)
- 很高兴看到人们欣赏古典音乐。-)
- 在Java中你在哪里得到随机226位?抱歉,您的代码没有回答这个问题。
- @托尔斯滕斯。"您的问题中已经有一个隐式假设,您可以以某种方式生成64位,所以只需执行您要执行的操作,只需执行四次。"
- @如果你没有一个"真"的随机数生成器,高雄就不会工作。问题是,下一个状态依赖于前一个状态,输出是确定的。一些prng,如mersenne twister,没有这个问题,因为它们有624个独立的元素,但它们也需要一个真正的随机种子。JavaRead()有效地总是48位,不需要提取多少位。您可以从序列12345678中提取千位…或23456789…,但有效信息的位长度仅为1或2。
- 我不明白你的意思,Java随机化()也不会提供64位的熵。操作意味着一个未指定的源,它可以产生64位来为prng种子。假设您可以向同一个源请求226位,这是有意义的。
- 思考:如果你用一个new Random()洗牌,然后用另一个new Random()洗牌,结果会怎样?是否包含足够数量的随机位?
你的分析是正确的:伪随机数发生器注入到与任何序列特异性种子必须是the same洗牌后,限the number of that to obtain你可以置换264。This is to verify experimentally assertion容易通过电话Collection.shuffle模式两次,在Random对象initialized with the same that the双J ^ Al和种子,identical shuffles是随机的。P></
在这一解决方案,然后,使用随机数发生器is to that for a空气日期2010年1月17 allows种子。"这可能是SecureRandomJava类提供了initialized virtually无限阵列与byte[]of size。然后你可以安审通SecureRandomto to the Collections.shuffle学院全面工作。P></
- 我是否认为字节[]应该是另一个随机种子,我应该从熵测量中得到(温度、位置、时间的函数)?
- 当然,一颗大种子并不能保证所有52颗!可能会产生(这个问题具体是关于什么的)?作为一个思想实验,考虑一个病理性的prng,它接受一个任意大的种子并产生一个无限长的零序列。很明显,prng需要满足更多的需求,而不仅仅是获取足够大的种子。
- @ Serjaldovic是的,传递给SoCurrand对象的任何种子材料必须是不可预测的,就像Java文档一样。
- @dasbinklenlight-现在我必须回答另一个问题:有多少字节,也就是说,字节的长度应该是多少,以确保我最终能够生成52的每一个可能!
- @NPE你是对的,虽然种子太小是上限的保证,但种子足够大并不是下限的保证。所有这些都是删除了理论上限,使RNG有可能生成全部52个!组合。
- @serjardovic最小的字节数是29(您需要226位来代表52!可能的位组合,即28.25字节,所以我们必须将其四舍五入)。请注意,使用29字节的种子材料可以在不确定下限的情况下,消除可以获得的随机播放次数的理论上限(请参见NPE关于一个需要非常大的种子并生成一个所有零序列的糟糕RNG的评论)。
- @Dasbinklenlight——你说"没有确定下限"是什么意思?它不是要给我全部52个吗?可能性从0到最大?
- @Serjardovic这不是RNG的工作原理,它们是从一个数字(种子)生成随机数的公式,它们不会迭代所有可能的数字序列。
- SecureRandom的实施几乎肯定会使用底层prng。它取决于prng的周期(以及在较小程度上,状态长度),它是否能够从52个阶乘排列中进行选择。(请注意,文档中说,SecureRandom的实现"最低限度地符合"某些统计测试,并生成"必须具有密码学强度"的输出,但对基础prng的状态长度或其周期没有明确的下限。)
- @serjardovic可用排列数是prng状态空间大小的函数,而不是其种子值。种子只是进入状态空间的入口或起点。另一篇文章见本答案的第二段。
- 一个足够新的SecureRandom应该至少有256位的内部状态,因为128位散列现在都被破坏了。
- @彼得罗。看看我的答案。
通用伪随机数产生器中,choose from(PRNG)不要求在置换52 item list of a length is less than if(226位了。P></
西安java.util.Random算法与modulus implements of length is its 248只;thus了48位多,我知道的所有比特less than the 226。You will need to another PRNG的使用已与大长& mdash;专门开发周期,一个与52或大的综合大学。P></
"我也看到shuffling article number"的在线随机发电机组。P></
consideration this is of the Nature of the PRNG独立;applies equally to cryptographic和伪随机数发生器(OF)的noncryptographic发生器是不恰当的信息,当noncryptographic security is involved)。P></
尽管allows种子java.security.SecureRandom无限长度在passed to be of the implementation,能安下SecureRandom使用PRNG(例如,"sha1prng"或"drbg")。恩,prng'在线和depends周期(S和extent to a较小,无论是capable of length)52选择从置换的综合要求。(注我"被定义"最大长度"as the size of the can take to初始化PRNG种子在其被压缩,或没有shortening种子")。P></
让我提前道歉,因为这有点难理解…
首先,你已经知道java.util.Random并不是完全随机的。它从种子中以完全可预测的方式生成序列。您完全正确,因为种子只有64位长,所以它只能生成2^64个不同的序列。如果您要以某种方式生成64个真正的随机位,并使用它们来选择一个种子,那么您就不能使用该种子在52个种子中进行随机选择!概率相等的可能序列。
但是,只要您实际生成的序列不超过2^64个,只要它可以生成的2^64个序列没有"特殊"或"明显特殊"的话,这个事实就没有任何意义。
假设您有一个更好的prng,使用1000位种子。假设您有两种方法来初始化它——一种方法是使用整个种子对其进行初始化,另一种方法是在初始化之前将种子散列到64位。
如果你不知道哪个初始值设定项是哪个,你能写任何测试来区分它们吗?除非你足够幸运地用相同的64位初始化了坏的初始值,否则答案是否定的。如果你不知道具体的prng实现中的一些弱点,你就无法区分这两个初始值。
或者,假设Random类有一个由2^64个序列组成的数组,这些序列在遥远的过去的某个时间被完全随机选择,并且种子只是这个数组的一个索引。
因此,事实上,Random只使用64位作为其种子并不一定是一个统计上的问题,只要您不太可能使用相同的种子两次。
当然,出于加密目的,64位种子是不够的,因为让系统使用相同的种子两次是计算上可行的。
编辑:
我要补充的是,尽管上述所有内容都是正确的,但java.util.Random的实际实现并不令人敬畏。如果你正在写一个纸牌游戏,可以使用MessageDigestAPI生成"MyGameName"+System.currentTimeMillis()的sha-256散列,并使用这些位来洗牌。根据上述论点,只要你的用户不是真正的赌博者,你就不必担心currentTimeMillis会返回很长时间。如果你的用户真的在赌博,那么使用没有种子的SecureRandom。
- 错,错,错。如果你不知道哪个初始值设定项是哪个,你能写任何测试来区分它们吗?是的,你可以。在64位的情况下,可能存在永远不会出现的卡组合。我想你不明白问题的严重性。OP想要一个工作系统,而不是看起来像随机的、有内在缺陷的系统。
- @托尔斯滕斯,你怎么能写下任何一种测试,可以确定有卡组合永远不会出现?
- 有几个随机数测试套件,如George Marsaglia的Diehard或Pierre L'Ecuyer/Richard Simard的TestU01,它们很容易在随机输出中发现统计异常。对于卡片检查,可以使用两个正方形。你决定卡的顺序。第一个方块显示前两张牌作为xy对的位置:第一张牌作为x,差(!)第二张牌的位置(-26-25)为Y。第二个方块显示第三张牌和第四张牌,相对于第二张牌和第三张牌(-25-25)。如果运行一段时间,这将立即显示分布中的间隙和簇。
- 好吧,这不是你说你可以写的测试,但也不适用。为什么您假设这样的测试会发现分布中存在空白和集群?正如我所提到的,这意味着"PRNG实施中的特定弱点",与可能的种子数量毫无关系。这样的测试甚至不需要你重新设置发电机。一开始我就警告说这很难理解。
- @托尔斯滕斯。这些测试套件绝对不能确定您的源代码是64位种子加密的安全prng还是真正的rng。(毕竟,测试prng是这些套件的用途。)即使您知道正在使用的算法,一个好的prng也会使您无法在没有对状态空间进行强力搜索的情况下确定状态。
- @托尔斯滕斯:在一副真正的牌中,绝大多数组合永远不会出现。你只是不知道那些是什么。对于一个半正派的prng来说,它是一样的——如果你能测试一个给定的输出序列,那么它的图像中是否有这么长的序列,这就是prng中的一个缺陷。荒谬的巨大的国家/时期,比如52年!不需要;128位就足够了。
- MaTimTimMaNs我编写了一个小程序来演示我所说的,唯一的问题是Java(它实现了一个线性同余生成器)的随机化(Knuth)的算法,它是惊人的顽固。我的想法是通过所有排列来证明有些是不可到达的,但到目前为止,对于52*51*50=132000,还没有奏效。这听起来不多,但这是一个优惠券集合问题,意味着需要150万次迭代。我需要重新思考如何证明这个问题。
- @托尔斯滕斯。考虑一下你所经历的困难告诉你,你所建议的是一个简单的测试。注意,有了三张牌,你仍然只消耗了17比特的纯熵。
- @托尔斯滕斯。我确实提到过那个特定的发电机不是很好。64位种子的优点是:从生成器返回的256位的每个块都是sha-256(concat("myrng",seed,n)),其中n是自生成器种子化以来返回的块数。
- @sneftel确实没有说什么,因为有坏的RNG很容易显示出这样的特征(见randu)。请等我证明我的弱点。
- @托尔斯滕斯。如果你想证明LCG-prng中产生排列的模式,我建议你换个方向,用fisher-yates洗牌。
- Java中的随机只使用48位
我要在这个问题上采取不同的策略。你的假设是正确的-你的prng不能达到全部52个!可能性。
问题是:你的纸牌游戏的规模是多少?
如果你在做一个简单的克朗代克风格的游戏?那你肯定不需要全部52个!可能性。相反,这样看:一个玩家将有18个五分之一的不同游戏。即使考虑到"生日问题",他们也必须在遇到第一个重复游戏之前玩数十亿只手。
如果你在做蒙特卡洛模拟?那你可能没事了。你可能需要处理由于prng中的"p"而产生的工件,但是你可能不会仅仅因为种子空间太小而遇到问题(同样,你也会看到无数种独特的可能性)。另一方面,如果你正在处理大量的迭代,那么,是的,你的种子空间太小可能会破坏交易。
如果你在玩多人纸牌游戏,特别是在网上有钱的情况下?然后你需要谷歌搜索一下在线扑克网站是如何处理你所问的问题的。因为对于普通玩家来说,低种子空间的问题并不明显,但是如果值得花时间投资的话,它是可以利用的。(扑克网站都经历了一个阶段,他们的prng被"黑客",让某人看到所有其他玩家的洞牌,只需从暴露的牌中推断种子。)如果这是你所处的情况,不要简单地找到一个更好的prng-你需要把它作为一个加密问题认真对待。
与dasbinklenlight基本相同的简短解决方案:
你不需要担心内部状态。详细解释原因:
通过这种方式创建SecureRandom实例时,它访问特定于操作系统的真随机数发生器。这要么是一个熵池,其中的值是包含随机位的存取(例如,对于纳秒计时器,纳秒精度基本上是随机的)或内部硬件编号生成器。
此输入!!)它可能仍然包含虚假的痕迹加密的强散列,用于删除这些跟踪。这就是使用这些CSPRNgs的原因,而不是为了自己创建这些数字!SecureRandom有一个计数器,跟踪使用了多少位(getBytes()、getLong()等),必要时用熵位重新填充SecureRandom。
简而言之:简单地忽略反对意见,使用SecureRandom作为真正的随机数生成器。
如果你只是考虑as the number of an位阵列(或字节),那么也许你可以用the Random.nextBytes(安全)问题解决方案在这suggested栈溢出,然后进入new BigInteger(byte[])地图数组。P></
一个非常简单的算法是将sha-256应用于从0向上递增的整数序列。(如果想要"得到不同的序列",可以添加一个盐)。)如果我们假设sha-256的输出是"和"0到2256-1之间的均匀分布整数一样好,那么我们就有足够的熵来完成任务。
要从sha256的输出中得到排列(当表示为整数时),只需将其模化为52、51、50…在这个伪代码中:
1 2 3 4 5 6 7 8 9 10
| deck = [0..52]
shuffled = []
r = SHA256(i)
while deck.size > 0:
pick = r % deck.size
r = floor(r / deck.size)
shuffled.append(deck[pick])
delete deck[pick] |
号