Generando números aleatorios en Objective-C

741

Soy una cabeza de Java principalmente, y quiero una forma de generar un número pseudoaleatorio entre 0 y 74. En Java usaría el método:

Random.nextInt(74)

No estoy interesado en una discusión sobre semillas o aleatoriedad verdadera, sino cómo se realiza la misma tarea en Objective-C. He buscado en Google, y parece que hay mucha información diferente y conflictiva.

rustyshelf
fuente

Respuestas:

1025

Deberías usar la arc4random_uniform()función. Utiliza un algoritmo superior para rand. Ni siquiera necesita establecer una semilla.

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

La arc4randompágina del 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
fuente
96
Use arc4random_uniform(x)como se describe a continuación por @yood. También está en stdlib.h (después de OS X 10.7 y iOS 4.3) y ofrece una distribución más uniforme de los números aleatorios. Usoint r = arc4random_uniform(74);
LavaSlider
44
NB: la distribución de arc4random puede ser muy pobre, si elige un rango pobre. No me había dado cuenta de las expectativas de los poderes de dos. +1 para usar la versión de @yood - hizo una diferencia notable para números más grandes (por ejemplo, rango de 400)
Adam
¿Genera solo números de 32 bits?
jjxtra
44
@codecowboy No lo hace. Siempre devuelve un entero en el rango [0, (2 ^ 32) -1]. Es el módulo que limita el límite superior del rango al número que especifique.
Motasim
1
¿No daría esto un número aleatorio entre 0 y 73?
Tom Howard
424

Use la arc4random_uniform(upper_bound)función para generar un número aleatorio dentro de un rango. Lo siguiente generará un número entre 0 y 73 inclusive.

arc4random_uniform(74)

arc4random_uniform(upper_bound)evita el sesgo de módulo como se describe en la página del manual:

arc4random_uniform () devolverá un número aleatorio distribuido uniformemente menor que upper_bound. arc4random_uniform () se recomienda sobre construcciones como `` arc4random ()% upper_bound '' ya que evita el " sesgo de módulo " cuando el límite superior no es una potencia de dos.

yood
fuente
32
Tenga en cuenta que arc4random_uniform () requiere iOS 4.3. En caso de que sea compatible con dispositivos más antiguos, debe agregar una verificación: #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_3 si falla la verificación, recurra a otra solución.
Ron
66
arc4random_uniform () requiere 10.7 o posterior también. Se bloquea su aplicación en 10.6
Tibidabo
@Tibidabo Su comentario es muy falso y engañoso. Estoy cansado de usar arc4random_uniform () en iOS 10.3 y no hay problemas. No requiere 10.7 o posterior
Cuarto
1
@ Cuarto No hay tal cosa como iOS 10.7, es macOS 10.7. Han pasado más de 5 años desde que escribí el comentario, era max iOS 5 en ese entonces.
Tibidabo el
63

Igual que C, harías

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

(suponiendo que quisieras incluir 0 pero excluyendo 74, que es lo que hace tu ejemplo de Java)

Editar: Siéntase libre de sustituir random()o arc4random()por rand()(que, como otros han señalado, es bastante desagradable).

James Ko
fuente
43
-1. Necesita sembrar el generador de números aleatorios o obtendrá el mismo patrón de números en cada ejecución.
Alex Reynolds, el
¿Qué tal si quiero comenzar desde un número diferente a cero?
amok
2
@amok: Simplemente puede agregar el número desde el que desea comenzar al resultado
Florin
2
Sigo recibiendo el número 9. bastante aleatorio diría; D
alexyorke
acabo de probar random (), y mostró el mismo problema que rand ()
LolaRun
50

Pensé que podría agregar un método que utilizo en muchos proyectos.

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

Si termino usándolo en muchos archivos, generalmente declaro una macro como

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

P.ej

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

Nota: solo para iOS 4.3 / OS X v10.7 (Lion) y posterior

Groot
fuente
La suma es conmutativa, incluso en fijos con enteros binarios que usan complemento de dos. Como tal, max - min + 1 es exactamente lo mismo que max + 1 - min y 1 + max - min.
Michael Morris
44

Esto le dará un número de coma flotante entre 0 y 47

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

O simplemente

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

Tanto el límite inferior como el superior también pueden ser negativos . El siguiente código de ejemplo le proporciona un número aleatorio entre -35.76 y +12.09

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

Convierta el resultado en un valor entero más redondo :

int intRndValue = (int)(rndValue + 0.5);
Tibidabo
fuente
Esto es malo. ¿Por qué estás usando flotador y no doble? Y si sus valores de coma flotante son de 0 a 47, entonces (int) (rndValue + 0.5) convertirá solo valores de 0.0 a 0.5 a 0, pero los valores de 0.5 a 1.5 se convertirán a 1, etc. Entonces los números 0 y 47 aparecerá solo la mitad de las veces que todos los demás números.
gnasher729
@ gnasher729 Lo siento, no entiendo tu punto. Obviamente, si necesita doble precisión, puede reemplazar fácilmente "flotación" con "doble"
Tibidabo
No creo que sea un gran problema. Si necesita valores enteros solamente, puede usar arc4random () / 0x100000000) * (high_bound-low_bound) + low_bound eliminando la conversión flotante / doble.
Tibidabo
Obtiene sesgo cuando convierte enteros a coma flotante de esta manera. Utilice en su drand48lugar para puntos flotantes.
Franklin Yu
37

Según la página del manual de rand (3), la familia de funciones rand ha quedado obsoleta al azar (3). Esto se debe al hecho de que los 12 bits inferiores de rand () pasan por un patrón cíclico. Para obtener un número aleatorio, simplemente siembra el generador llamando a srandom () con una semilla sin signo, y luego llama al azar (). Entonces, el equivalente del código anterior sería

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

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

Solo necesitará llamar a srandom () una vez en su programa a menos que quiera cambiar su semilla. Aunque dijiste que no querías una discusión sobre valores verdaderamente aleatorios, rand () es un generador de números aleatorios bastante malo, y random () todavía sufre sesgos de módulo, ya que generará un número entre 0 y RAND_MAX. Entonces, por ejemplo, si RAND_MAX es 3 y desea un número aleatorio entre 0 y 2, tiene el doble de probabilidades de obtener un 0 que un 1 o un 2.

Michael Buckley
fuente
77
También podría llamar a srandomdev () en lugar de pasar el tiempo a srandom (); es igual de fácil y matemáticamente mejor.
benzado
31

Mejor usar arc4random_uniform . Sin embargo, esto no está disponible por debajo de iOS 4.3. Afortunadamente, iOS vinculará este símbolo en tiempo de ejecución, no en tiempo de compilación (así que no use la directiva de preprocesador #if para verificar si está disponible).

La mejor manera de determinar si arc4random_uniformestá disponible es hacer algo como esto:

#include <stdlib.h>

int r = 0;
if (arc4random_uniform != NULL)
    r = arc4random_uniform (74);
else
    r = (arc4random() % 74);
AW101
fuente
99
Esta pregunta es sobre Objective-C, que utiliza enlace tardío. A diferencia de C, que se une en tiempo de compilación / enlace, Objective-C vincula símbolos en tiempo de ejecución, y los símbolos que no se pueden vincular se establecen en NULL. Si bien tiene razón en que esto no es válido C, sin duda es válido Objective-C. Uso este código exacto en mi aplicación para iPhone. [PD, por favor, ¿puedes corregir tu voto negativo?].
AW101
Si bien el objetivo-c usa el enlace tardío para los métodos objc, para una función C este no es el caso. Este código ciertamente se bloqueará si la función no existe en tiempo de ejecución.
Richard J. Ross III
8
Según Apple "... el vinculador establece la dirección de las funciones no disponibles en NULL ...", consulte el listado 3.2: developer.apple.com/library/mac/#documentation/DeveloperTools/… . Ok, entonces tiene que estar débilmente vinculado, pero no se bloquea.
AW101
1
Comprobar que la dirección de una función es NULL es un método que se ha utilizado en todas las versiones de C, C ++ y Objective-C, tanto en MacOS X como en iOS.
gnasher729
14

Escribí mi propia clase de utilidad de números aleatorios solo para tener algo que funcionara un poco más como Math.random () en Java. Tiene solo dos funciones, y todo está hecho en C.

Archivo de cabecera:

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

Archivo de implementación:

//Random.m
static unsigned long seed;

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

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

Es una forma bastante clásica de generar pseudo-randoms. En mi delegado de aplicaciones llamo:

#import "Random.h"

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

Luego más tarde solo digo:

float myRandomNumber = nextRandomFloat() * 74;

Tenga en cuenta que este método devuelve un número aleatorio entre 0.0f (inclusive) y 1.0f (exclusivo).

Eli
fuente
3
1. Las funciones de números aleatorios creadas al azar generalmente no son muy aleatorias. 2. Está totalmente roto en un procesador de 64 bits. 3. El uso de segundos desde 1970 como semilla aleatoria hace que los números sean predecibles.
gnasher729
7

Ya hay algunas respuestas geniales y articuladas, pero la pregunta pide un número aleatorio entre 0 y 74. Use:

arc4random_uniform(75)

Tom Howard
fuente
4

A partir de iOS 9 y OS X 10.11, puedes usar las nuevas clases de GameplayKit para generar números aleatorios de varias maneras.

Tiene cuatro tipos de fuente para elegir: una fuente aleatoria general (sin nombre, hasta el sistema para elegir lo que hace), lineal congruencial, ARC4 y Mersenne Twister. Estos pueden generar entradas aleatorias, flotadores y bools.

En el nivel más simple, puede generar un número aleatorio a partir de la fuente aleatoria incorporada del sistema de esta manera:

NSInteger rand = [[GKRandomSource sharedRandom] nextInt];

Eso genera un número entre -2,147,483,648 y 2,147,483,647. Si desea un número entre 0 y un límite superior (exclusivo) usaría esto:

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

GameplayKit tiene algunos constructores convenientes integrados para trabajar con dados. Por ejemplo, puedes tirar un dado de seis lados como este:

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

Además, puede dar forma a la distribución aleatoria utilizando cosas como GKShuffledDistribution.

TwoStraws
fuente
Mersenne es más rápido y mejor para cosas como el desarrollo de juegos donde la calidad del número aleatorio generado generalmente no es demasiado importante.
Robert Wasmann
3

Genere un número aleatorio entre 0 y 99:

int x = arc4random()%100;

Genere un número aleatorio entre 500 y 1000:

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

// El siguiente ejemplo generará un número entre 0 y 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);

Salida:

  • número aleatorio: 72

  • número aleatorio paso 2: 52

soumya
fuente
1

Para el desarrollo del juego, use random () para generar randoms. Probablemente al menos 5 veces más rápido que usar arc4random (). El sesgo de módulo no es un problema, especialmente para juegos, cuando se generan randoms usando el rango completo de random (). Asegúrese de sembrar primero. Llame a srandomdev () en AppDelegate. Aquí hay algunas funciones de ayuda:

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
fuente
Sin embargo, tenga en cuenta que random () no es tan aleatorio, por lo que si la velocidad no es importante en su código (como si solo se usa de vez en cuando), use arc4random ().
Robert Wasmann