การสร้างตัวเลขสุ่มใน Objective-C


741

ฉันเป็นหัวหน้าส่วนใหญ่ของ Java และฉันต้องการวิธีในการสร้างตัวเลขสุ่มหลอกระหว่าง 0 และ 74 ใน Java ฉันจะใช้วิธีการ:

Random.nextInt(74)

ฉันไม่ได้สนใจในการอภิปรายเกี่ยวกับเมล็ดหรือการสุ่มอย่างแท้จริงเพียงวิธีที่คุณทำภารกิจเดียวกันใน Objective-C ฉันกำจัด Google แล้วและดูเหมือนว่ามีข้อมูลที่แตกต่างและขัดแย้งกันมากมาย

คำตอบ:


1025

คุณควรใช้arc4random_uniform()ฟังก์ชั่น randมันใช้อัลกอริทึมที่เหนือกว่า คุณไม่จำเป็นต้องตั้งค่าแม้แต่เมล็ด

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

arc4randomหน้าคน:

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
ใช้arc4random_uniform(x)ตามที่อธิบายไว้ด้านล่างโดย @yood มันยังอยู่ใน stdlib.h (หลัง OS X 10.7 และ iOS 4.3) และให้การกระจายของตัวเลขสุ่มที่สม่ำเสมอยิ่งขึ้น การใช้งานint r = arc4random_uniform(74);
LavaSlider

4
หมายเหตุ: การกระจายจาก arc4 สุ่มอาจแย่มากหากคุณเลือกช่วงที่ไม่ดี ฉันไม่ได้ตระหนักถึงความคาดหวังพลังแห่งสอง +1 สำหรับการใช้งาน @ yood's version - สร้างความแตกต่างที่เห็นได้ชัดเจนสำหรับตัวเลขขนาดใหญ่ (เช่นช่วง 400)
Adam

มันสร้างตัวเลข 32 บิตเท่านั้นหรือไม่
jjxtra

4
@codecowboy มันไม่ได้ มันจะคืนค่าจำนวนเต็มเสมอในช่วง [0, (2 ^ 32) -1] มันเป็นโมดูโล่ที่ จำกัด ขอบเขตบนของช่วงให้เป็นจำนวนที่คุณระบุ
Motasim

1
นี่จะให้ตัวเลขสุ่มระหว่าง 0 ถึง 73 หรือไม่
Tom Howard

424

ใช้arc4random_uniform(upper_bound)ฟังก์ชั่นเพื่อสร้างตัวเลขสุ่มภายในช่วง ต่อไปนี้จะสร้างตัวเลขระหว่าง 0 ถึง 73

arc4random_uniform(74)

arc4random_uniform(upper_bound)หลีกเลี่ยงอคติ moduloตามที่อธิบายไว้ในหน้าคน:

arc4random_uniform () จะส่งคืนหมายเลขสุ่มที่กระจายแบบสม่ำเสมอน้อยกว่า upper_bound arc4random_uniform () ถูกแนะนำเหนือสิ่งปลูกสร้างเช่น `` arc4random ()% Upper_bound '' เนื่องจากมันหลีกเลี่ยง " modulo bias " เมื่อขอบเขตบนไม่ใช่พลังของสอง


32
โปรดทราบว่า arc4random_uniform () ต้องใช้ iOS 4.3 ในกรณีที่คุณสนับสนุนอุปกรณ์รุ่นเก่าคุณควรเพิ่มการตรวจสอบ: #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_3 หากการตรวจสอบล้มเหลวให้กลับไปใช้โซลูชันอื่น
Ron

6
arc4random_uniform () ต้องใช้ 10.7 หรือใหม่กว่าเช่นกัน แอปของคุณขัดข้องใน 10.6
Tibidabo

@Tibidabo ความคิดเห็นของคุณเป็นเท็จมากและทำให้เข้าใจผิด ฉันเหนื่อยกับการใช้ arc4random_uniform () บน iOS 10.3 และไม่มีปัญหา ไม่ต้องการ 10.7 หรือใหม่กว่า
ที่สี่

1
@ สี่ไม่มีอะไรเช่น iOS 10.7 เป็น macOS 10.7 เป็นเวลานานกว่า 5 ปีแล้วที่ฉันเขียนความคิดเห็นมันเป็น iOS สูงสุด 5 แล้ว
Tibidabo

63

เช่นเดียวกับ C คุณจะทำ

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

(สมมติว่าคุณหมายถึงการรวม 0 แต่ไม่รวม 74 ซึ่งเป็นตัวอย่าง Java ของคุณ)

แก้ไข:รู้สึกฟรีเพื่อทดแทนrandom()หรือarc4random()เพื่อrand()(ซึ่งก็คือตามที่คนอื่นได้ชี้ให้เห็นค่อนข้างน่าเบื่อ)


43
-1 คุณจำเป็นต้องสร้างตัวสร้างตัวเลขสุ่มหรือคุณจะได้รูปแบบของตัวเลขที่เหมือนกันในแต่ละการดำเนินการ
Alex Reynolds

ฉันต้องการเริ่มจากตัวเลขอื่นที่ไม่ใช่ศูนย์
amok

2
@amok: คุณสามารถเพิ่มหมายเลขที่คุณต้องการเริ่มจากผลลัพธ์
Florin

2
ฉันได้หมายเลข 9 มา
เรื่อย ๆ

ฉันเพิ่งทดสอบแบบสุ่ม () และมันก็แสดงให้เห็นว่าปัญหาเดียวกันกับแรนด์ ()
LolaRun

50

ฉันคิดว่าฉันสามารถเพิ่มวิธีการที่ฉันใช้ในหลายโครงการ

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

ถ้าฉันใช้มันในหลาย ๆ ไฟล์ฉันมักจะประกาศแมโครเป็น

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

เช่น

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

หมายเหตุ: สำหรับ iOS 4.3 / OS X v10.7 (Lion) และใหม่กว่าเท่านั้น


การเติมเป็นแบบสับเปลี่ยนแม้จะถูกแก้ไขด้วยเลขจำนวนเต็มไบนารีโดยใช้ twos-complement เช่น max - min + 1 นั้นเหมือนกับ max + 1 - min และ 1 + max - min
Michael Morris

44

นี่จะทำให้คุณมีจำนวนจุดลอยตัวระหว่าง 0 ถึง 47

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

หรือเพียงแค่

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

ทั้งขอบเขตล่างและบนอาจเป็นลบได้เช่นกัน โค้ดตัวอย่างด้านล่างให้ตัวเลขสุ่มระหว่าง -35.76 ถึง +12.09

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

แปลงผลลัพธ์เป็นค่าที่เป็นจำนวนเต็ม :

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

นี้ไม่ดี. ทำไมคุณใช้ลอยและไม่เพิ่มเป็นสองเท่า และถ้าค่าทศนิยมของคุณอยู่ระหว่าง 0 ถึง 47 ดังนั้น (int) (rndValue + 0.5) จะแปลงเฉพาะค่าตั้งแต่ 0.0 ถึง 0.5 ถึง 0 แต่ค่าจาก 0.5 เป็น 1.5 จะถูกแปลงเป็น 1 เป็นต้นดังนั้นตัวเลข 0 และ 47 จะเกิดขึ้นเพียงครึ่งเดียวกับจำนวนอื่น ๆ ทั้งหมด
gnasher729

@ gnasher729 ขออภัยฉันไม่เห็นจุดของคุณ เห็นได้ชัดว่าถ้าคุณต้องการความแม่นยำสองเท่าคุณสามารถแทนที่ "float" ด้วย "double" ได้อย่างง่ายดาย
Tibidabo

ฉันไม่คิดว่ามันเป็นเรื่องใหญ่ หากคุณต้องการค่าจำนวนเต็มเท่านั้นที่คุณสามารถใช้ arc4random () / 0x100000000) * (high_bound-low_bound) + low_bound โดยการลบการแปลงทศนิยม / สองครั้ง
Tibidabo

คุณได้รับอคติเมื่อคุณแปลงจำนวนเต็มเป็นทศนิยมด้วยวิธีนี้ ใช้drand48แทนคะแนนลอย
Franklin Yu

37

ตามหน้าคู่มือสำหรับ rand (3) ตระกูล rand ของฟังก์ชั่นนั้นล้าสมัยโดยการสุ่ม (3) นี่เป็นเพราะความจริงที่ว่าแรนด์ต่ำกว่า 12 บิต () ผ่านรูปแบบวงจร ในการรับหมายเลขสุ่มเพียงแค่สร้างเครื่องกำเนิดไฟฟ้าโดยการเรียก srandom () ด้วยเมล็ดที่ไม่ได้ลงนามแล้วโทรสุ่ม () ดังนั้นเทียบเท่ากับรหัสข้างต้นจะเป็น

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

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

คุณจะต้องโทร srandom () หนึ่งครั้งในโปรแกรมของคุณเว้นแต่คุณต้องการเปลี่ยนเมล็ดพันธุ์ แม้ว่าคุณจะบอกว่าคุณไม่ต้องการพูดคุยเกี่ยวกับค่าสุ่มอย่างแท้จริง แต่ rand () เป็นตัวสร้างตัวเลขสุ่มที่แย่มากและ random () ยังคงทนทุกข์ทรมานจาก modulo bias เพราะมันจะสร้างตัวเลขระหว่าง 0 ถึง RAND_MAX ดังนั้นเช่นหาก RAND_MAX เป็น 3 และคุณต้องการตัวเลขสุ่มระหว่าง 0 ถึง 2 คุณจะมีโอกาสได้รับ 0 มากกว่า 1 หรือ 2


7
คุณอาจเรียก srandomdev () แทนการส่งผ่านไปยัง srandom () มันง่ายกว่าและดีกว่าทางคณิตศาสตร์
benzado

31

arc4random_uniformดีกว่าที่จะใช้ อย่างไรก็ตามนี่ไม่สามารถใช้งานได้กับ iOS 4.3 โชคดีที่ iOS จะผูกสัญลักษณ์นี้ไว้ที่รันไทม์ไม่ใช่เวลารวบรวม (ดังนั้นอย่าใช้คำสั่ง preprocessor #if เพื่อตรวจสอบว่ามีหรือไม่)

วิธีที่ดีที่สุดในการตรวจสอบว่าarc4random_uniformมีให้ทำหรือไม่:

#include <stdlib.h>

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

9
คำถามนี้เกี่ยวกับ Objective-C ซึ่งใช้การรวมภายหลัง ซึ่งแตกต่างจาก C ซึ่งผูกที่เวลารวบรวม / ลิงค์สัญลักษณ์ Objective-C ผูกรันไทม์และสัญลักษณ์ที่ผูกไม่สามารถตั้งค่าเป็น NULL แม้ว่าคุณจะถูกต้องว่านี่ไม่ถูกต้อง C แต่แน่นอนที่สุดคือ Objective-C ที่ถูกต้อง ฉันใช้รหัสที่แน่นอนนี้ในแอพ iPhone ของฉัน [ps โปรดแก้ไข downvote ของคุณ]
AW101

ในขณะที่วัตถุประสงค์ -c ใช้การเชื่อมโยงล่าช้าสำหรับเมธอด objc สำหรับฟังก์ชั่น C นี่ไม่ใช่กรณี รหัสนี้จะผิดพลาดอย่างแน่นอนหากฟังก์ชั่นไม่ได้อยู่ในเวลาทำงาน
Richard J. Ross III

8
ตามที่แอปเปิ้ล "... ลิงเกอร์ตั้งอยู่ของฟังก์ชั่นการใช้งานไม่ได้เป็นโมฆะ ..." ดูรายชื่อ 3.2: developer.apple.com/library/mac/#documentation/DeveloperTools/... ตกลงดังนั้นจะต้องมีการเชื่อมโยงที่อ่อนแอ แต่ก็ไม่ผิดพลาด
AW101

1
การตรวจสอบว่าที่อยู่ของฟังก์ชั่นเป็น NULL เป็นวิธีการที่ใช้ใน C, C ++ และ Objective-C ทุกรุ่นทั้งใน MacOS X และ iOS
gnasher729

14

ฉันเขียนคลาสยูทิลิตี้ตัวเลขสุ่มของตัวเองเพื่อที่ฉันจะได้มีอะไรบางอย่างที่ทำหน้าที่เหมือน Math.random () ใน Java มันมีแค่สองฟังก์ชั่นและมันทำในซี

ไฟล์ส่วนหัว:

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

ไฟล์การใช้งาน:

//Random.m
static unsigned long seed;

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

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

มันเป็นวิธีที่คลาสสิกในการสร้างหลอกหลอก ในตัวแทนแอพของฉันฉันโทร:

#import "Random.h"

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

หลังจากนั้นฉันก็พูดว่า:

float myRandomNumber = nextRandomFloat() * 74;

โปรดทราบว่าวิธีการนี้จะส่งกลับตัวเลขสุ่มระหว่าง 0.0f (รวม) และ 1.0f (พิเศษ)


3
1. ฟังก์ชั่นตัวเลขสุ่มที่สร้างโดยการสุ่มมักจะไม่สุ่มมาก 2. มันเสียโดยสิ้นเชิงในโปรเซสเซอร์ 64 บิต 3. การใช้วินาทีตั้งแต่ปี 1970 เป็นเมล็ดสุ่มทำให้สามารถคาดเดาตัวเลขได้
gnasher729

7

มีคำตอบที่ยอดเยี่ยมและชัดเจนเป็นอย่างดีอยู่แล้ว แต่คำถามนั้นถามหมายเลขสุ่มระหว่าง 0 ถึง 74 การใช้:

arc4random_uniform(75)


4

ตั้งแต่ iOS 9 และ OS X 10.11 คุณสามารถใช้คลาส GameplayKit ใหม่เพื่อสร้างตัวเลขสุ่มได้หลายวิธี

คุณมีแหล่งที่มาสี่ประเภทให้เลือก: แหล่งสุ่มทั่วไป (ไม่มีชื่อลงไปที่ระบบเพื่อเลือกว่ามันทำอะไร), เชิงเส้นเชิงเส้นตรง, ARC4 และ Mersenne Twister สิ่งเหล่านี้สามารถสร้าง ints แบบสุ่มลอยและบูลส์

ในระดับที่ง่ายที่สุดคุณสามารถสร้างตัวเลขสุ่มจากแหล่งสุ่มในตัวของระบบดังนี้:

NSInteger rand = [[GKRandomSource sharedRandom] nextInt];

ที่สร้างตัวเลขระหว่าง -2,147,483,648 ถึง 2,147,483,647 หากคุณต้องการตัวเลขระหว่าง 0 ถึงขอบเขตสูงสุด (พิเศษ) คุณจะใช้สิ่งนี้:

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

GameplayKit มีตัวสร้างความสะดวกสบายบางอย่างในตัวเพื่อทำงานกับลูกเต๋า ตัวอย่างเช่นคุณสามารถหมุนแม่พิมพ์แบบหกด้านดังนี้:

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

GKShuffledDistributionพลัสที่คุณสามารถสร้างรูปร่างการกระจายแบบสุ่มโดยใช้สิ่งที่ต้องการ


Mersenne นั้นเร็วที่สุดและดีที่สุดสำหรับสิ่งต่างๆเช่นเกม dev ที่คุณภาพของตัวเลขสุ่มที่สร้างขึ้นนั้นไม่สำคัญนัก
Robert Wasmann

3

สร้างหมายเลขสุ่มระหว่าง 0 ถึง 99:

int x = arc4random()%100;

สร้างหมายเลขสุ่มระหว่าง 500 ถึง 1,000:

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

1

// ตัวอย่างต่อไปนี้จะสร้างตัวเลขระหว่าง 0 ถึง 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);

เอาท์พุท:

  • หมายเลขสุ่ม: 72

  • จำนวนสุ่มขั้นตอนที่ 2: 52


1

สำหรับเกม dev ใช้ random () เพื่อสร้าง randoms อาจเร็วกว่า 5x5 อย่างน้อยเมื่อใช้ arc4random () Modulo bias ไม่ใช่ปัญหาโดยเฉพาะอย่างยิ่งสำหรับเกมเมื่อสร้าง randoms โดยใช้ช่วงเต็มของการสุ่ม () ต้องแน่ใจว่าได้เพาะเมล็ดก่อน โทร srandomdev () ใน AppDelegate นี่คือฟังก์ชั่นตัวช่วยบางตัว:

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

โปรดทราบว่าการสุ่ม () นั้นไม่ค่อยสุ่มดังนั้นหากความเร็วไม่สำคัญในรหัสของคุณ (เช่นถ้าใช้เพียงครั้งเดียวในขณะนั้น) จากนั้นใช้ arc4random ()
Robert Wasmann
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.