Acho que tenho quatro respostas, duas dando soluções exatas como a de @Adam Rosenfield, mas sem o problema do loop infinito, e outras duas com solução quase perfeita, mas com implementação mais rápida que a primeira.
A melhor solução exata requer 7 chamadas rand5
, mas vamos prosseguir para entender.
Método 1 - Exato
A força da resposta de Adam é que ela fornece uma distribuição uniforme perfeita e há uma probabilidade muito alta (21/25) de que apenas duas chamadas para rand5 () serão necessárias. No entanto, o pior caso é o loop infinito.
A primeira solução abaixo também fornece uma distribuição uniforme perfeita, mas requer um total de 42 chamadas para rand5
. Sem loops infinitos.
Aqui está uma implementação do R:
rand5 <- function() sample(1:5,1)
rand7 <- function() (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1
Para pessoas que não estão familiarizadas com o R, aqui está uma versão simplificada:
rand7 = function(){
r = 0
for(i in 0:6){
r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
}
return r %% 7 + 1
}
A distribuição de rand5
será preservada. Se fizermos as contas, cada uma das 7 iterações do loop possui 5 ^ 6 combinações possíveis, portanto, o número total de combinações possíveis é (7 * 5^6) %% 7 = 0
. Assim, podemos dividir os números aleatórios gerados em grupos iguais de 7. Veja o método dois para mais discussão sobre isso.
Aqui estão todas as combinações possíveis:
table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
15625 15625 15625 15625 15625 15625 15625
Eu acho que é fácil mostrar que o método de Adam será muito mais rápido. A probabilidade de haver 42 chamadas ou mais rand5
na solução de Adam é muito pequena ( (4/25)^21 ~ 10^(-17)
).
Método 2 - Não exato
Agora, o segundo método, que é quase uniforme, mas requer 6 chamadas para rand5
:
rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1
Aqui está uma versão simplificada:
rand7 = function(){
r = 0
for(i in 1:6){
r = r + i*rand5()
}
return r %% 7 + 1
}
Esta é essencialmente uma iteração do método 1. Se gerarmos todas as combinações possíveis, aqui estão as contagens resultantes:
table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
2233 2232 2232 2232 2232 2232 2232
Um número aparecerá mais uma vez nos 5^6 = 15625
testes.
Agora, no método 1, adicionando 1 a 6, movemos o número 2233 para cada um dos pontos sucessivos. Assim, o número total de combinações corresponderá. Isso funciona porque 5 ^ 6 %% 7 = 1 e, depois, fazemos 7 variações apropriadas (7 * 5 ^ 6 %% 7 = 0).
Método 3 - Exato
Se o argumento dos métodos 1 e 2 for entendido, o método 3 segue e requer apenas 7 chamadas para rand5
. Nesse ponto, acho que esse é o número mínimo de chamadas necessárias para uma solução exata.
Aqui está uma implementação do R:
rand5 <- function() sample(1:5,1)
rand7 <- function() (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1
Para pessoas que não estão familiarizadas com o R, aqui está uma versão simplificada:
rand7 = function(){
r = 0
for(i in 1:7){
r = r + i * rand5()
}
return r %% 7 + 1
}
A distribuição de rand5
será preservada. Se fizermos as contas, cada uma das 7 iterações do loop tem 5 resultados possíveis, portanto, o número total de combinações possíveis (7 * 5) %% 7 = 0
. Assim, podemos dividir os números aleatórios gerados em grupos iguais de 7. Veja o método um e dois para mais discussão sobre isso.
Aqui estão todas as combinações possíveis:
table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
5 5 5 5 5 5 5
Eu acho que é fácil mostrar que o método de Adam ainda funcionará mais rápido. A probabilidade de que haja 7 ou mais chamadas rand5
na solução de Adam ainda é pequena ( (4/25)^3 ~ 0.004
).
Método 4 - Não exato
Essa é uma variação menor do segundo método. É quase uniforme, mas requer 7 chamadas para rand5
, isto é mais um para o método 2:
rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1
Aqui está uma versão simplificada:
rand7 = function(){
r = 0
for(i in 1:6){
r = r + i*rand5()
}
return (r+rand5()) %% 7 + 1
}
Se gerarmos todas as combinações possíveis, aqui estão as contagens resultantes:
table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
11160 11161 11161 11161 11161 11161 11160
Dois números aparecerão uma vez menos nos 5^7 = 78125
testes. Para a maioria dos propósitos, eu posso viver com isso.