Gerando números aleatórios no Objective-C

741

Sou principalmente Java, e quero uma maneira de gerar um número pseudo-aleatório entre 0 e 74. Em Java, usaria o método:

Random.nextInt(74)

Não estou interessado em uma discussão sobre sementes ou verdadeira aleatoriedade, exatamente como você realiza a mesma tarefa no Objective-C. Eu vasculhei o Google e parece haver muitas informações diferentes e conflitantes.

enferrujado
fonte

Respostas:

1025

Você deve usar a arc4random_uniform()função Ele usa um algoritmo superior para rand. Você nem precisa colocar uma semente.

#include <stdlib.h>
// ...
// ...
int r = arc4random_uniform(74);

A arc4randompágina do manual:

NAME
     arc4random, arc4random_stir, arc4random_addrandom -- arc4 random number generator

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <stdlib.h>

     u_int32_t
     arc4random(void);

     void
     arc4random_stir(void);

     void
     arc4random_addrandom(unsigned char *dat, int datlen);

DESCRIPTION
     The arc4random() function uses the key stream generator employed by the arc4 cipher, which uses 8*8 8
     bit S-Boxes.  The S-Boxes can be in about (2**1700) states.  The arc4random() function returns pseudo-
     random numbers in the range of 0 to (2**32)-1, and therefore has twice the range of rand(3) and
     random(3).

     The arc4random_stir() function reads data from /dev/urandom and uses it to permute the S-Boxes via
     arc4random_addrandom().

     There is no need to call arc4random_stir() before using arc4random(), since arc4random() automatically
     initializes itself.

EXAMPLES
     The following produces a drop-in replacement for the traditional rand() and random() functions using
     arc4random():

           #define foo4random() (arc4random() % ((unsigned)RAND_MAX + 1))
lajos
fonte
96
Use arc4random_uniform(x)como descrito abaixo por @yood. Também está em stdlib.h (depois do OS X 10.7 e iOS 4.3) e fornece uma distribuição mais uniforme dos números aleatórios. Usoint r = arc4random_uniform(74);
LavaSlider
4
Nota: a distribuição do arc4random pode ser muito ruim, se você escolher um intervalo ruim. Eu não tinha percebido a expectativa dos poderes de dois. 1 para utilização @ versão de Yood - fez uma diferença perceptível para números maiores (por exemplo, gama de 400)
Adam
Ele gera apenas números de 32 bits?
12132 jjxtra
4
@codecowboy Isso não acontece. Ele sempre retorna um número inteiro no intervalo [0, (2 ^ 32) -1]. É o módulo que limita o limite superior do intervalo ao número que você especificar.
Motasim 23/03
1
Isso não daria um número aleatório entre 0 e 73?
Tom Howard
424

Use a arc4random_uniform(upper_bound)função para gerar um número aleatório dentro de um intervalo. A seguir, será gerado um número entre 0 e 73, inclusive.

arc4random_uniform(74)

arc4random_uniform(upper_bound)evita viés de módulo, conforme descrito na página de manual:

arc4random_uniform () retornará um número aleatório uniformemente distribuído menor que upper_bound. O arc4random_uniform () é recomendado em construções como `` arc4random ()% upper_bound '', pois evita " viés de módulo " quando o limite superior não é uma potência de dois.

yood
fonte
32
Observe que arc4random_uniform () requer o iOS 4.3. Caso você ofereça suporte a dispositivos mais antigos, adicione uma verificação: #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_3 Se a verificação falhar, volte para outra solução.
Ron
6
arc4random_uniform () também requer 10.7 ou posterior. Ele trava seu aplicativo na versão 10.6
Tibidabo
@Tibidabo Seu comentário é muito falso e enganoso. Eu apenas cansei de usar arc4random_uniform () no iOS 10.3 e não há problemas. Não requer 10.7 ou posterior #
3030 de quarta
1
@ Quarto Não existe o iOS 10.7, é o macOS 10.7. Faz mais de 5 anos desde que escrevi o comentário que era o máximo do iOS 5 naquela época.
Tibidabo
63

O mesmo que C, você faria

#include <time.h>
#include <stdlib.h>
...
srand(time(NULL));
int r = rand() % 74;

(supondo que você quis dizer incluindo 0, mas excluindo 74, que é o que seu exemplo de Java faz)

Edit: Sinta-se livre para substituir random()ou arc4random()para rand()(o que é, como outros apontaram, muito sucky).

James Ko
fonte
43
-1. Você precisa propagar o gerador de números aleatórios ou obterá o mesmo padrão de números em cada execução.
Alex Reynolds
Que tal começar com um número diferente de zero?
amok
2
@amok: Você pode simplesmente adicionar o número do qual deseja iniciar o resultado
Florin
2
Recebo o número 9. bastante aleatória eu diria; D
alexyorke
i apenas testado aleatório (), e mostrou-se o mesmo problema que rand ()
LolaRun
50

Eu pensei que poderia adicionar um método que eu uso em muitos projetos.

- (NSInteger)randomValueBetween:(NSInteger)min and:(NSInteger)max {
    return (NSInteger)(min + arc4random_uniform(max - min + 1));
}

Se eu acabar usando em muitos arquivos, normalmente declaro uma macro como

#define RAND_FROM_TO(min, max) (min + arc4random_uniform(max - min + 1))

Por exemplo

NSInteger myInteger = RAND_FROM_TO(0, 74) // 0, 1, 2,..., 73, 74

Nota: Apenas para iOS 4.3 / OS X v10.7 (Lion) e posterior

Groot
fonte
A adição é comutativa, mesmo em números inteiros binários fixos usando complemento de dois. Assim, max - min + 1 é exatamente o mesmo que max + 1 - min e também 1 + max - min.
9788 Michael Morris
44

Isso fornecerá um número de ponto flutuante entre 0 e 47

float low_bound = 0;      
float high_bound = 47;
float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);

Ou simplesmente

float rndValue = (((float)arc4random()/0x100000000)*47);

Os limites inferior e superior também podem ser negativos . O código de exemplo abaixo fornece um número aleatório entre -35,76 e +12,09

float low_bound = -35.76;      
float high_bound = 12.09;
float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);

Converta o resultado em um valor inteiro mais redondo :

int intRndValue = (int)(rndValue + 0.5);
Tibidabo
fonte
Isto é mau. Por que você está usando float, e não o dobro? E se seus valores de ponto flutuante forem de 0 a 47, (int) (rndValue + 0,5) converterá apenas valores de 0,0 a 0,5 a 0, mas valores de 0,5 a 1,5 serão convertidos em 1 etc. Portanto, os números 0 e 47 surgirá apenas metade da freqüência que todos os outros números.
gnasher729
@ gnasher729 Desculpe, não entendi seu ponto. Obviamente, se você precisa de precisão dupla você pode facilmente substituir "float", com "duplo"
Tibidabo
Eu não acho que isso é grande coisa. Se você precisar de valores apenas inteiros, poderá usar arc4random () / 0x100000000) * (high_bound-low_bound) + low_bound removendo a conversão flutuante / dupla.
Tibidabo
Você obtém um viés ao converter inteiro para ponto flutuante dessa maneira. Use em drand48vez disso para pontos flutuantes.
Franklin Yu
37

De acordo com a página de manual para rand (3), a família de funções rand foi obsoleta por acaso (3). Isso se deve ao fato de os 12 bits inferiores de rand () passarem por um padrão cíclico. Para obter um número aleatório, basta propagar o gerador chamando srandom () com uma semente não assinada e, em seguida, chame random (). Portanto, o equivalente ao código acima seria

#import <stdlib.h>
#import <time.h>

srandom(time(NULL));
random() % 74;

Você só precisará chamar srandom () uma vez no seu programa, a menos que queira alterar sua semente. Embora você tenha dito que não queria uma discussão sobre valores verdadeiramente aleatórios, rand () é um gerador de números aleatórios bastante ruim, e random () ainda sofre com o viés do módulo, pois gera um número entre 0 e RAND_MAX. Portanto, por exemplo, se RAND_MAX é 3 e você deseja um número aleatório entre 0 e 2, é duas vezes mais provável que você obtenha um 0 que um 1 ou um 2.

Michael Buckley
fonte
7
Você também pode chamar srandomdev () em vez de passar o tempo para srandom (); é tão fácil e matematicamente melhor.
benzado 04/02
31

Melhor usar arc4random_uniform. No entanto, isso não está disponível abaixo do iOS 4.3. Felizmente, o iOS vincula esse símbolo no tempo de execução, não no tempo de compilação (portanto, não use a diretiva #if pré-processador para verificar se está disponível).

A melhor maneira de determinar se arc4random_uniformestá disponível é fazer algo assim:

#include <stdlib.h>

int r = 0;
if (arc4random_uniform != NULL)
    r = arc4random_uniform (74);
else
    r = (arc4random() % 74);
AW101
fonte
9
Esta pergunta é sobre Objective-C, que usa ligação tardia. Diferentemente de C, que liga no tempo de compilação / link, Objective-C liga símbolos em tempo de execução e símbolos que não podem ser ligados são definidos como NULL. Enquanto você estiver certo de que isso não é válido C, certamente é válido Objective-C. Eu uso esse código exato no meu aplicativo para iPhone. [ps, por favor, você pode corrigir seu voto negativo].
AW101
Enquanto o objetivo-c usa ligação tardia para métodos objc, para uma função C, esse não é o caso. Esse código certamente falhará se a função não existir no tempo de execução.
Richard J. Ross III
8
De acordo com a Apple "... o vinculador define o endereço das funções indisponíveis como NULL ...", consulte a lista 3.2: developer.apple.com/library/mac/#documentation/DeveloperTools/… . Ok, então ele deve estar fracamente vinculado, mas não trava.
AW101
1
Verificar se o endereço de uma função é NULL é um método usado em todas as versões de C, C ++ e Objective-C, tanto no MacOS X quanto no iOS.
gnasher729
14

Eu escrevi minha própria classe de utilitário de número aleatório apenas para ter algo que funcionasse um pouco mais como Math.random () em Java. Ele tem apenas duas funções, e tudo é feito em C.

Arquivo de cabeçalho:

//Random.h
void initRandomSeed(long firstSeed);
float nextRandomFloat();

Arquivo de implementação:

//Random.m
static unsigned long seed;

void initRandomSeed(long firstSeed)
{ 
    seed = firstSeed;
}

float nextRandomFloat()
{
    return (((seed= 1664525*seed + 1013904223)>>16) / (float)0x10000);
}

É uma maneira bastante clássica de gerar pseudo-randoms. No delegado do meu aplicativo, eu chamo:

#import "Random.h"

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    initRandomSeed( (long) [[NSDate date] timeIntervalSince1970] );
    //Do other initialization junk.
}

Depois, apenas digo:

float myRandomNumber = nextRandomFloat() * 74;

Observe que esse método retorna um número aleatório entre 0,0f (inclusive) e 1,0f (exclusivo).

Eli
fonte
3
1. As funções numéricas aleatórias criadas aleatoriamente geralmente não são muito aleatórias. 2. Está totalmente quebrado em um processador de 64 bits. 3. Usar segundos desde 1970 como a semente aleatória torna os números previsíveis.
gnasher729
7

Já existem respostas ótimas e articuladas, mas a pergunta pede um número aleatório entre 0 e 74. Use:

arc4random_uniform(75)

Tom Howard
fonte
4

No iOS 9 e OS X 10.11, você pode usar as novas classes GameplayKit para gerar números aleatórios de várias maneiras.

Você tem quatro tipos de fonte para escolher: uma fonte aleatória geral (sem nome, até o sistema escolher o que faz), congruencial linear, ARC4 e Mersenne Twister. Isso pode gerar entradas, flutuadores e bools aleatórios.

No nível mais simples, você pode gerar um número aleatório a partir da fonte aleatória interna do sistema, como esta:

NSInteger rand = [[GKRandomSource sharedRandom] nextInt];

Isso gera um número entre -2.147.483.648 e 2.147.483.647. Se você deseja um número entre 0 e um limite superior (exclusivo), use este:

NSInteger rand6 = [[GKRandomSource sharedRandom] nextIntWithUpperBound:6];

O GameplayKit possui alguns construtores de conveniência incorporados para trabalhar com dados. Por exemplo, você pode rolar um dado de seis lados assim:

GKRandomDistribution *d6 = [GKRandomDistribution d6];
[d6 nextInt];

Além disso, você pode moldar a distribuição aleatória usando coisas como GKShuffledDistribution.

TwoStraws
fonte
Mersenne é o mais rápido e melhor para coisas como desenvolvimento de jogos, onde a qualidade do número aleatório gerado geralmente não é muito importante.
Robert Wasmann
3

Gere um número aleatório entre 0 e 99:

int x = arc4random()%100;

Gere um número aleatório entre 500 e 1000:

int x = (arc4random()%501) + 500;
adijazz91
fonte
1

// O exemplo a seguir vai gerar um número entre 0 e 73.

int value;
value = (arc4random() % 74);
NSLog(@"random number: %i ", value);

//In order to generate 1 to 73, do the following:
int value1;
value1 = (arc4random() % 73) + 1;
NSLog(@"random number step 2: %i ", value1);

Resultado:

  • número aleatório: 72

  • número aleatório passo 2: 52

soumya
fonte
1

Para desenvolvedores de jogos, use random () para gerar randoms. Provavelmente pelo menos 5x mais rápido do que usar arc4random (). O viés do módulo não é um problema, especialmente para jogos, ao gerar randoms usando toda a gama aleatória (). Certifique-se de semear primeiro. Chame srandomdev () no AppDelegate. Aqui estão algumas funções auxiliares:

static inline int random_range(int low, int high){ return (random()%(high-low+1))+low;}
static inline CGFloat frandom(){ return (CGFloat)random()/UINT32_C(0x7FFFFFFF);}
static inline CGFloat frandom_range(CGFloat low, CGFloat high){ return (high-low)*frandom()+low;}
Robert Wasmann
fonte
Esteja ciente de que random () não é tão aleatório, portanto, se a velocidade não for importante em seu código (como se fosse usado apenas de vez em quando), use arc4random ().
Robert Wasmann