我在解决以下问题时遇到了一些麻烦。
您可以从标准的52张卡片组中抽牌,而无需替换,直到获得一张A。您从剩余的剩余数中提取,直到得到2。继续进行3.整个甲板用完后,您期望的剩余数字是多少?
让它自然
因此,问题本质上是要弄清楚当甲板用尽时您将处于的概率,即:
我理解了
但无法再继续下去...
2AAA2
我在解决以下问题时遇到了一些麻烦。
您可以从标准的52张卡片组中抽牌,而无需替换,直到获得一张A。您从剩余的剩余数中提取,直到得到2。继续进行3.整个甲板用完后,您期望的剩余数字是多少?
让它自然
因此,问题本质上是要弄清楚当甲板用尽时您将处于的概率,即:
我理解了
但无法再继续下去...
2AAA2
Answers:
遵循@gung的想法,我相信期望值为5.84?从我对评论的解释来看,我假设“ A”几乎是不可能的值(除非副牌中的最后四张牌都是A)。这是100,000次迭代蒙特卡洛模拟的结果
results
2 3 4 5 6 7 8 9 J K Q T
1406 7740 16309 21241 19998 15127 9393 4906 976 190 380 2334
如果您想使用它,这里是R代码。
# monte carlo card-drawing functions from here
# http://streaming.stat.iastate.edu/workshops/r-intro/lectures/5-Rprogramming.pdf
# create a straightforward deck of cards
create_deck <-
function( ){
suit <- c( "H" , "C" , "D" , "S" )
rank <- c( "A" , 2:9 , "T" , "J" , "Q" , "K" )
deck <- NULL
for ( r in rank ) deck <- c( deck , paste( r , suit ) )
deck
}
# construct a function to shuffle everything
shuffle <- function( deck ){ sample( deck , length( deck ) ) }
# draw one card at a time
draw_cards <-
function( deck , start , n = 1 ){
cards <- NULL
for ( i in start:( start + n - 1 ) ){
if ( i <= length( deck ) ){
cards <- c( cards , deck[ i ] )
}
}
return( cards )
}
# create an empty vector for your results
results <- NULL
# run your simulation this many times..
for ( i in seq( 100000 ) ){
# create a new deck
sdeck <- shuffle( create_deck() )
d <- sdeck[ grep('A|2' , sdeck ) ]
e <- identical( grep( "2" , d ) , 1:4 )
# loop through ranks in this order
rank <- c( "A" , 2:9 , "T" , "J" , "Q" , "K" )
# start at this position
card.position <- 0
# start with a blank current.draw
current.draw <- ""
# start with a blank current rank
this.rank <- NULL
# start with the first rank
rank.position <- 1
# keep drawing until you find the rank you wanted
while( card.position < 52 ){
# increase the position by one every time
card.position <- card.position + 1
# store the current draw for testing next time
current.draw <- draw_cards( sdeck , card.position )
# if you draw the current rank, move to the next.
if ( grepl( rank[ rank.position ] , current.draw ) ) rank.position <- rank.position + 1
# if you have gone through every rank and are still not out of cards,
# should it still be a king? this assumes yes.
if ( rank.position == length( rank ) ) break
}
# store the rank for this iteration.
this.rank <- rank[ rank.position ]
# at the end of the iteration, store the result
results <- c( results , this.rank )
}
# print the final results
table( results )
# make A, T, J, Q, K numerics
results[ results == 'A' ] <- 1
results[ results == 'T' ] <- 10
results[ results == 'J' ] <- 11
results[ results == 'Q' ] <- 12
results[ results == 'K' ] <- 13
results <- as.numeric( results )
# and here's your expected value after 100,000 simulations.
mean( results )
A
不可能?考虑48张牌的顺序,AAAA
例如后面的顺序。
1/prod( 48:1 / 52:5 )
results
对于仿真,正确和快速至关重要。 这两个目标都建议编写针对编程环境核心功能的代码以及尽可能短而简单的代码,因为简单有助于提高清晰度,而清晰度则可以提高正确性。这是我在以下方面实现两者的尝试R
:
#
# Simulate one play with a deck of `n` distinct cards in `k` suits.
#
sim <- function(n=13, k=4) {
deck <- sample(rep(1:n, k)) # Shuffle the deck
deck <- c(deck, 1:n) # Add sentinels to terminate the loop
k <- 0 # Count the cards searched for
for (j in 1:n) {
k <- k+1 # Count this card
deck <- deck[-(1:match(j, deck))] # Deal cards until `j` is found
if (length(deck) < n) break # Stop when sentinels are reached
}
return(k) # Return the number of cards searched
}
可以在设置随机数种子之后使用该函数以可重现的方式应用它replicate
,如
> set.seed(17); system.time(d <- replicate(10^5, sim(13, 4)))
user system elapsed
5.46 0.00 5.46
这很慢,但是足够快,可以反复进行相当长的(因此很精确)仿真,而无需等待。我们可以通过几种方式展示结果。让我们从其平均值开始:
> n <- length(d)
> mean(d)
[1] 5.83488
> sd(d) / sqrt(n)
[1] 0.005978956
我们可能还希望查看频率列表(及其标准误差)。以下代码稍微修饰了列表:
u <- table(d)
u.se <- sqrt(u/n * (1-u/n)) / sqrt(n)
cards <- c("A", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K")
dimnames(u) <- list(sapply(dimnames(u), function(x) cards[as.integer(x)]))
print(rbind(frequency=u/n, SE=u.se), digits=2)
这是输出:
2 3 4 5 6 7 8 9 T J Q K
frequency 0.01453 0.07795 0.1637 0.2104 0.1995 0.1509 0.09534 0.04995 0.02249 0.01009 0.00345 0.00173
SE 0.00038 0.00085 0.0012 0.0013 0.0013 0.0011 0.00093 0.00069 0.00047 0.00032 0.00019 0.00013
n
k
draw <- function(deck) {
n <- length(sentinels <- sort(unique(deck)))
deck <- c(deck, sentinels)
k <- 0
for (j in sentinels) {
k <- k+1
deck <- deck[-(1:match(j, deck))]
if (length(deck) < n) break
}
return(k)
}
(可以draw
在任何地方使用sim
,但是在开始时draw
进行的额外工作使其速度是的两倍sim
。)
我们可以通过将其应用于给定套牌的每个不同的随机播放来使用它。由于此处的目的只是一次测试,因此生成这些混洗的效率并不重要。这是一种快速的暴力方式:
n <- 4 # Distinct cards
k <- 2 # Number of suits
d <- expand.grid(lapply(1:(n*k), function(i) 1:n))
e <- apply(d, 1, function(x) var(tabulate(x))==0)
g <- apply(d, 1, function(x) length(unique(x))==n)
d <- d[e & g,]
现在d
是一个数据行,其行包含所有混洗。应用于draw
每一行并计算结果:
d$result <- apply(as.matrix(d), 1, draw)
(counts <- table(d$result))
输出(我们将在正式测试中暂时使用)为
2 3 4
420 784 1316
sim
>set.seed(17)
>d.sim <- replicate(10^4, sim(n, k))
>print((rbind(table(d.sim) / length(d.sim), counts / dim(d)[1])), digits=3)
2 3 4
[1,] 0.168 0.312 0.520
[2,] 0.167 0.311 0.522
> chisq.test(table(d.sim), p=counts / dim(d)[1])
Chi-squared test for given probabilities
data: table(d.sim)
X-squared = 0.2129, df = 2, p-value = 0.899
sim
sim
最后,两个样本的卡方检验将比较的输出与sim
另一个答案中报告的输出:
>y <- c(1660,8414,16973,21495,20021,14549,8957,4546,2087,828,313,109)
>chisq.test(cbind(u, y))
data: cbind(u, y)
X-squared = 142.2489, df = 11, p-value < 2.2e-16
巨大的卡方统计量得出的p值基本上为零:毫无疑问,sim
不同意其他答案。 有两种可能的解决方案:这些答案之一(或两者都有!)不正确,或者它们对问题的理解不同。例如,我将“ 纸牌用完后 ”解释为是指观察最后一张卡之后,如果允许的话,请在终止程序之前更新“您将要使用的号码”。可以想象,最后一步并不是要采取的。也许这种解释上的细微差别可以解释分歧,在这一点上,我们可以修改问题,使问题更清晰。
有一个确切的答案(以矩阵乘积的形式,在下面的第4点中给出)。从以下观察中得出,存在一种合理有效的算法来对其进行计算:
通过仅对ace进行混洗,然后(应用第一个观察结果)将两个,然后三个,等等依次穿插,可以将此问题视为十三步链。
我们确实需要跟踪的不仅仅是我们所寻找的卡的价值。但是,在执行此操作时,我们不需要考虑标记相对于所有卡的位置,而只需考虑其相对于等值或较小值的卡的位置。
想象一下在第一个王牌上放置一个标记,然后标记在它之后的前两个,依此类推。(如果甲板在任何阶段都没用完而没有显示我们当前正在寻找的牌,我们将不标记所有牌。)让每个标记的“位置”(如果存在)是等于或小于该值的牌数是在标记时(包括标记的卡本身)进行交易的。 这些地方包含所有基本信息。
这篇文章的其余部分提供了详细信息,在中提供了一个可行的实现R
,并以有关问题和解决方案效率的一些评论作为结尾。
被称为套牌的“组合”。
让我们更新该图以反映这种情况:
其中总和始于并结束于。(此总和的可变长度表明存在除非在特殊情况下,否则不太可能是和的函数的封闭公式。)
最初存在概率,该位置将是和概率它将具有任何其它可能的值在。这可以由向量。
散布在下后卡,载体更新为由转换矩阵相乘(在左侧)。重复此过程,直到所有张卡片都已放置。在每个阶段,概率向量条目的总和是某个卡片被标记的机会。因此,使值等于剩余值是在步骤之后没有留下标记的机会p 1 p 2(Pr k 1,k 2(q | p ),。因此,这些值的连续差异为我们提供了我们找不到要标记的类型的牌的可能性:即当游戏结束时牌组耗尽时,我们正在寻找的牌的值的概率分布。
以下R
代码实现了该算法。它与前面的讨论类似。首先,转换概率的计算方式为t.matrix
(无需通过除法进行归一化,从而更容易在测试代码时跟踪计算结果):
t.matrix <- function(q, p, n, k) {
j <- max(0, q-(n+1)):min(k-1, q-(p+1))
return (sum(choose(p-1+j,j) * choose(n+k-q, k-1-j))
}
用来transition
将更新为。它计算转换矩阵并执行乘法。如果参数为空向量,它还将负责计算初始向量:p
#
# `p` is the place distribution: p[i] is the chance the place is `i`.
#
transition <- function(p, k) {
n <- length(p)
if (n==0) {
q <- c(1, rep(0, k-1))
} else {
#
# Construct the transition matrix.
#
t.mat <- matrix(0, nrow=n, ncol=(n+k))
#dimnames(t.mat) <- list(p=1:n, q=1:(n+k))
for (i in 1:n) {
t.mat[i, ] <- c(rep(0, i), sapply((i+1):(n+k),
function(q) t.matrix(q, i, n, k)))
}
#
# Normalize and apply the transition matrix.
#
q <- as.vector(p %*% t.mat / choose(n+k, k))
}
names(q) <- 1:(n+k)
return (q)
}
现在,我们可以轻松地计算任何牌组在每个阶段的非标记概率:
#
# `k` is an array giving the numbers of each card in order;
# e.g., k = rep(4, 13) for a standard deck.
#
# NB: the *complements* of the p-vectors are output.
#
game <- function(k) {
p <- numeric(0)
q <- sapply(k, function(i) 1 - sum(p <<- transition(p, i)))
names(q) <- names(k)
return (q)
}
它们是标准卡座的:
k <- rep(4, 13)
names(k) <- c("A", 2:9, "T", "J", "Q", "K")
(g <- game(k))
输出是
A 2 3 4 5 6 7 8 9 T J Q K
0.00000000 0.01428571 0.09232323 0.25595013 0.46786622 0.66819134 0.81821790 0.91160622 0.96146102 0.98479430 0.99452614 0.99818922 0.99944610
根据规则,如果标记了国王,那么我们将不再寻找其他牌:这意味着必须将的值增加到。这样做后,差异将得出“甲板用尽时将要使用的数字”的分布:
> g[13] <- 1; diff(g)
2 3 4 5 6 7 8 9 T J Q K
0.014285714 0.078037518 0.163626897 0.211916093 0.200325120 0.150026562 0.093388313 0.049854807 0.023333275 0.009731843 0.003663077 0.001810781
(将其与我在另一个描述蒙特卡洛模拟的答案中报告的输出进行比较:它们看起来是相同的,直到预期的随机变化量为止。)
期望值是立即的:
> sum(diff(g) * 2:13)
[1] 5.832589
总而言之,这只需要十几行左右的可执行代码。我已经根据手工计算检查了较小值(最多)。因此,如果代码与问题的先前分析之间出现明显差异,请信任该代码(因为分析可能存在印刷错误)。
与其他序列的关系
当每张卡中都有一张时,分布是整数的倒数序列:
> 1/diff(game(rep(1,10)))
[1] 2 3 8 30 144 840 5760 45360 403200
在地方的价值就是(从处开始)。这是在线整数序列大全中的序列A001048。因此,我们可能希望有一个常数为的甲板(“适合的”甲板)的封闭公式来推广该序列,该序列本身具有深远的意义。(例如,它计算置换组中最大共轭类的大小,并且还与三项式系数有关。)(不幸的是,的一般化中的倒数通常不是整数。)
游戏是随机过程
我们的分析清楚地表明最初的的矢量的系数,,是恒定的。例如,让我们跟踪处理每组卡时的输出:game
> sapply(1:13, function(i) game(rep(4,i)))
[[1]]
[1] 0
[[2]]
[1] 0.00000000 0.01428571
[[3]]
[1] 0.00000000 0.01428571 0.09232323
[[4]]
[1] 0.00000000 0.01428571 0.09232323 0.25595013
...
[[13]]
[1] 0.00000000 0.01428571 0.09232323 0.25595013 0.46786622 0.66819134 0.81821790 0.91160622 0.96146102 0.98479430 0.99452614 0.99818922 0.99944610
例如,最终向量的第二个值(描述了52张牌的完整结果)已经在第二组处理之后出现(等于)。因此,如果只需要有关通过卡值进行的标记的信息,则只需对一副张卡进行计算。
由于随着增加,没有标记价值的牌的机会很快就接近,因此在四套衣服中有种类型的牌之后,我们几乎达到了预期的极限值。确实,极限值大约为(对于张张牌计算,这时双精度舍入误差会阻止进一步的运算)。
定时
查看应用于向量,我们看到其时序应与成比例,并且-使用粗略上限-与与成比例不差。通过对到和到所有计算进行计时,并仅分析花费较长时间(秒或更长时间)的计算,我估计计算时间约为,支持此上限评估。(ķ ,ķ ,... ,ķ )ķ 2 米3 ķ = 1 7 Ñ = 10 30 1 / 2 ø (ķ 2 Ñ 2.9)
这些渐近线的一种用途是预测较大问题的计算时间。例如,看到情况大约需要秒,我们可以估计,(非常有趣)情况大约需要秒。(实际上需要秒。)1.31 ķ = 1 ,Ñ = 100 1.31 (1 / 4 )2(100 / 30 )2.9 ≈ 2.7 2.87
#!/usr/bin/perl
use strict;
my @deck = (1..13) x 4;
my $N = 100000; # Monte Carlo iterations.
my $mean = 0;
for (my $i = 1; $i <= $N; $i++) {
my @d = @deck;
fisher_yates_shuffle(\@d);
my $last = 0;
foreach my $c (@d) {
if ($c == $last + 1) { $last = $c }
}
$mean += ($last + 1) / $N;
}
print $mean, "\n";
sub fisher_yates_shuffle {
my $array = shift;
my $i = @$array;
while (--$i) {
my $j = int rand($i + 1);
@$array[$i, $j] = @$array[$j, $i];
}
}