Generazione di numeri casuali in Objective-C


741

Sono principalmente un capo Java e voglio un modo per generare un numero pseudo-casuale tra 0 e 74. In Java userei il metodo:

Random.nextInt(74)

Non sono interessato a una discussione sui semi o sulla vera casualità, ma su come svolgi lo stesso compito in Objective-C. Ho cercato Google e sembra che ci siano molte informazioni diverse e contrastanti.

Risposte:


1025

Dovresti usare la arc4random_uniform()funzione. Utilizza un algoritmo superiore a rand. Non è nemmeno necessario impostare un seme.

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

La arc4randompagina man:

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))

96
Utilizzare arc4random_uniform(x)come descritto di seguito da @yood. È anche in stdlib.h (dopo OS X 10.7 e iOS 4.3) e offre una distribuzione più uniforme dei numeri casuali. Utilizzoint r = arc4random_uniform(74);
LavaSlider

4
NB: la distribuzione da arc4random può essere molto scarsa, se ti capita di scegliere un intervallo scarso. Non avevo realizzato l'aspettativa di due poteri. +1 per l'uso della versione di yood - ha fatto una notevole differenza per numeri più grandi (ad es. Intervallo di 400)
Adam

Genera solo numeri a 32 bit?
jjxtra,

4
@codecowboy Non lo fa. Restituisce sempre un numero intero nell'intervallo [0, (2 ^ 32) -1]. È il modulo che limita il limite superiore dell'intervallo al numero specificato.
Motasim,

1
Questo non darebbe un numero casuale compreso tra 0 e 73?
Tom Howard,

424

Utilizzare la arc4random_uniform(upper_bound)funzione per generare un numero casuale all'interno di un intervallo. Di seguito verrà generato un numero compreso tra 0 e 73 inclusi.

arc4random_uniform(74)

arc4random_uniform(upper_bound)evita distorsioni del modulo come descritto nella pagina man:

arc4random_uniform () restituirà un numero casuale uniformemente distribuito inferiore a upper_bound. arc4random_uniform () è raccomandato su costruzioni come `` arc4random ()% upper_bound '' poiché evita " distorsioni del modulo " quando il limite superiore non è una potenza di due.


32
Nota che arc4random_uniform () richiede iOS 4.3. Nel caso in cui si supportino dispositivi più vecchi, è necessario aggiungere un segno di spunta: in #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_3 caso contrario, ricorrere a un'altra soluzione.
Ron,

6
arc4random_uniform () richiede anche il 10.7 o successivo. Si blocca la tua app in 10.6
Tibidabo

@Tibidabo Il tuo commento è molto falso e fuorviante. Mi sono appena stancato di usare arc4random_uniform () su iOS 10.3 e non ci sono problemi. Non richiede il 10.7 o successivo
Quarto

1
@Fourth Non esiste iOS 10.7, è macOS 10.7. Sono passati più di 5 anni da quando ho scritto il commento che era max iOS 5 allora.
Tibidabo,

63

Come in C, lo faresti

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

(supponendo che intendessi includere 0 ma escluso 74, che è ciò che fa il tuo esempio Java)

Modifica: sentiti libero di sostituire random()o arc4random()per rand()(che, come altri hanno sottolineato, abbastanza schifoso).


43
-1. Devi seminare il generatore di numeri casuali o otterrai lo stesso modello di numeri su ogni esecuzione.
Alex Reynolds,

Che ne dici di voler iniziare da un numero diverso da zero?
Amok

2
@amok: puoi semplicemente aggiungere il numero da cui vuoi iniziare al risultato
Florin

2
Continuo a ricevere il numero 9. Abbastanza casuale direi: D
alexyorke il

ho appena testato random (), e ha mostrato lo stesso problema di rand ()
LolaRun

50

Pensavo di poter aggiungere un metodo che utilizzo in molti progetti.

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

Se finisco per usarlo in molti file di solito dichiaro una macro come

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

Per esempio

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

Nota: solo per iOS 4.3 / OS X v10.7 (Lion) e versioni successive


L'aggiunta è commutativa, anche su fisso con numeri interi binari usando il complemento a due. Pertanto, max - min + 1 equivale esattamente a max + 1 - min e 1 + max - min.
Michael Morris,

44

Questo ti darà un numero in virgola mobile compreso tra 0 e 47

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

O semplicemente

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

Anche il limite inferiore e superiore possono essere negativi . Il seguente codice di esempio fornisce un numero casuale compreso tra -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);

Converti il ​​risultato in un valore intero più arrotondato :

int intRndValue = (int)(rndValue + 0.5);

Questo non va bene. Perché stai usando float e non il doppio? E se i valori in virgola mobile sono compresi tra 0 e 47, allora (int) (rndValue + 0.5) convertirà solo i valori da 0,0 a 0,5 a 0, ma i valori da 0,5 a 1,5 verranno convertiti in 1 ecc. Quindi i numeri 0 e 47 arriverà solo la metà delle volte che tutti gli altri numeri.
gnasher729,

@ gnasher729 Mi dispiace non vedo il tuo punto. Ovviamente se hai bisogno di doppia precisione puoi facilmente sostituire "float" con "double"
Tibidabo

Non penso sia un grosso problema. Se hai bisogno di valori interi solo di quelli che puoi usare arc4random () / 0x100000000) * (high_bound-low_bound) + low_bound rimuovendo float / double conversion.
Tibidabo

In questo modo si ottiene distorsione quando si converte un numero intero in virgola mobile. Utilizzare drand48invece per i punti mobili.
Franklin Yu,

37

Secondo la pagina di manuale di rand (3), la famiglia di funzioni rand è stata superata da random (3). Ciò è dovuto al fatto che i 12 bit inferiori di rand () passano attraverso un modello ciclico. Per ottenere un numero casuale, esegui il seeding del generatore chiamando srandom () con un seed non firmato, quindi chiama random (). Quindi, l'equivalente del codice sopra sarebbe

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

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

Dovrai chiamare srandom () solo una volta nel tuo programma a meno che tu non voglia cambiare il tuo seed. Sebbene tu abbia detto che non volevi una discussione su valori veramente casuali, rand () è un generatore di numeri casuali piuttosto male, e random () soffre ancora di distorsioni del modulo, in quanto genererà un numero compreso tra 0 e RAND_MAX. Ad esempio, se RAND_MAX è 3 e desideri un numero casuale compreso tra 0 e 2, hai il doppio delle probabilità di ottenere uno 0 rispetto a 1 o 2.


7
Potresti anche chiamare srandomdev () invece di passare il tempo a srandom (); è altrettanto facile e matematicamente migliore.
benzado,

31

Meglio usare arc4random_uniform. Tuttavia, questo non è disponibile sotto iOS 4.3. Fortunatamente iOS vincolerà questo simbolo in fase di esecuzione, non in fase di compilazione (quindi non utilizzare la direttiva preprocessore #if per verificare se è disponibile).

Il modo migliore per determinare se arc4random_uniformè disponibile è fare qualcosa del genere:

#include <stdlib.h>

int r = 0;
if (arc4random_uniform != NULL)
    r = arc4random_uniform (74);
else
    r = (arc4random() % 74);

9
Questa domanda riguarda Objective-C che utilizza l'associazione tardiva. A differenza di C che si lega in fase di compilazione / collegamento, Objective-C lega i simboli in fase di esecuzione e i simboli che non può associare sono impostati su NULL. Mentre hai ragione sul fatto che questo non è C valido, sicuramente è valido Objective-C. Uso questo codice esatto nella mia app per iPhone. [ps, per favore, puoi correggere il tuo downvote].
AW101

Mentre oggettivo-c usa l'associazione tardiva per i metodi objc, per una funzione C non è così. Questo codice andrà sicuramente in crash se la funzione non esiste in fase di esecuzione.
Richard J. Ross III,

8
Secondo Apple "... il linker imposta l'indirizzo delle funzioni non disponibili su NULL ...", Vedi l'elenco 3.2: developer.apple.com/library/mac/#documentation/DeveloperTools/… . Ok, quindi deve essere debolmente collegato, ma non si arresta in modo anomalo.
AW101,

1
Verificare che l'indirizzo di una funzione sia NULL è un metodo utilizzato in tutte le versioni di C, C ++ e Objective-C sia su MacOS X che su iOS.
gnasher729,

14

Ho scritto la mia classe di utilità per i numeri casuali solo per avere qualcosa che funzionasse un po 'più come Math.random () in Java. Ha solo due funzioni ed è tutto realizzato in C.

File di intestazione:

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

File di implementazione:

//Random.m
static unsigned long seed;

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

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

È un modo piuttosto classico di generare pseudo-randomi. Nel delegato della mia app chiamo:

#import "Random.h"

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

Poi dopo dico solo:

float myRandomNumber = nextRandomFloat() * 74;

Si noti che questo metodo restituisce un numero casuale compreso tra 0,0f (incluso) e 1,0f (esclusivo).


3
1. Le funzioni di numero casuale create in modo casuale non sono in genere molto casuali. 2. È completamente rotto su un processore a 64 bit. 3. L'uso dei secondi dal 1970 come seme casuale rende prevedibili i numeri.
gnasher729,

7

Ci sono già alcune risposte grandi e articolate, ma la domanda richiede un numero casuale compreso tra 0 e 74. Usa:

arc4random_uniform(75)


4

A partire da iOS 9 e OS X 10.11, puoi utilizzare le nuove classi GameplayKit per generare numeri casuali in vari modi.

Puoi scegliere tra quattro tipi di sorgente: una fonte casuale generale (senza nome, fino al sistema per scegliere cosa fare), lineare congruenziale, ARC4 e Mersenne Twister. Questi possono generare ints, float e bool casuali.

Al livello più semplice, è possibile generare un numero casuale dalla fonte casuale incorporata del sistema in questo modo:

NSInteger rand = [[GKRandomSource sharedRandom] nextInt];

Ciò genera un numero compreso tra -2.147.483.648 e 2.147.483.647. Se si desidera un numero compreso tra 0 e un limite superiore (esclusivo), utilizzare questo:

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

GameplayKit ha alcuni costruttori di convenienza integrati per lavorare con i dadi. Ad esempio, puoi tirare un dado a sei facce in questo modo:

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

Inoltre puoi modellare la distribuzione casuale usando cose come GKShuffledDistribution.


Mersenne è la più veloce e la migliore per cose come lo sviluppatore di giochi in cui la qualità del numero casuale generato di solito non è troppo importante.
Robert Wasmann,

3

Genera un numero casuale compreso tra 0 e 99:

int x = arc4random()%100;

Genera un numero casuale tra 500 e 1000:

int x = (arc4random()%501) + 500;

1

// Il seguente esempio genererà un numero compreso tra 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);

Produzione:

  • numero casuale: 72

  • numero casuale passo 2: 52


1

Per gli sviluppatori di giochi usa random () per generare randoms. Probabilmente almeno 5 volte più veloce rispetto all'uso di arc4random (). La distorsione da modulo non è un problema, specialmente per i giochi, quando si generano random usando l'intera gamma di random (). Assicurati di seminare prima. Chiama srandomdev () in AppDelegate. Ecco alcune funzioni di supporto:

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;}

Ricorda però che random () non è così casuale, quindi se la velocità non è importante nel tuo codice (come se fosse usata solo una volta ogni tanto) usa arc4random ().
Robert Wasmann,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.