Question

I've a problem with the generation of random values with arc4random in Xcode. I want to exclude from the random generation those number that are already picked up. This is the algorithm that I've wroten

int randomValue =  (arc4random() % numberofquest)+ 1;
int kk=1;

if (kk==1) {

//first time add the first random value
[oldquest addObject: randomValue];
    }
else {
        //control if the number is already in the vector
        for (int j=1; j<[oldquest count]; j++)
        {

            if (randomValue==oldquest[j])
            {

                randomValue =  (arc4random() % numerodomande)+ 1;
            }
            else
            {
                [oldquest addObject: randomValue];
            }
        }

}
kk=kk+1

But It doesn't work. I suppose maybe because randomvalue and the j-th object in the array are not comparable (int the first and string the second?). Can anyone help me please?

Was it helpful?

Solution

I'm sorry, Ares answer was almost right. You can compare two different NSNumber objects.

Here is my solution:

- (int) generateRandomNumber {
    int number = arc4random() % 100;

    if ([chosen_numbers indexOfObject:[NSNumber numberWithInt:number]]!=NSNotFound)
        number = [self generateRandomNumber];

    [chosen_numbers addObject:[NSNumber numberWithInt:number]];
    return number;
}

Since Alessandro has some trouble implementing it heres an example inside a UIViewController class. This works for 100 numbers:

#import "ViewController.h"

@implementation ViewController {
    NSMutableArray * chosen_numbers;    
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    chosen_numbers = [[NSMutableArray alloc] init];

    for(int i = 0; i<90; i++) {
        NSLog(@"number: %d",[self generateRandomNumber]);
    }
}

- (int) generateRandomNumber {
    int number = arc4random() % 100;

    if ([chosen_numbers indexOfObject:[NSNumber numberWithInt:number]]!=NSNotFound)
        number = [self generateRandomNumber];

    [chosen_numbers addObject:[NSNumber numberWithInt:number]];
    return number;
}

@end

OTHER TIPS

How about this:

    -(void) generateRandomNumber {

        int randomValue =  arc4random_uniform(numberofquest) + 1;

        if([oldQuest indexOfObject:[NSNumber numberWithInt:randomValue] == NSNotFound) {
            //Unique value
            [oldQuest addObject:[NSNumber numberWithInt:randomValue]];
        }
        else {
            //Value already exists. Look for another one
            return [self generateRandomNumber];

        }
    }

Obviously oldQuest is NSMutableArray instance that was previously initialized.

You are right - it doesn't work because here:

            if (randomValue==oldquest[j])

you are trying to compare an int with an object... NSArrays can only store objects. In fact this line should not work for that reason:

[oldquest addObject: randomValue];

You need to box the int as an NSNumber and store that in the array:

NSNumber* boxedRandomValue = [NSNumber numberWithInt:randomValue];
[oldquest addObject: boxedRandomValue];

Then unbox it using the NSNumber instance method -(int)intValue before comparing values:

 if (randomValue==[oldquest[j] intValue])

update

There are a few other issues you will have to attend to:

  • the value of kk is reset to 1 on each iteration of the test, so kk == 1 is always true, the else clause is never invoked. You need to set it once only outside of this block of code (for example you could make it a property, set it to 1 on initialise, then access and increment it here). Better still, just use [oldquest count] instead: if ([oldquest count]==0) {} else {}. Then you can dispense with your kk counter altogether.

  • your for-loop starts with j=1. This should be j=0 to address the first item in the array (item 0).

update 2

This line: randomValue = (arc4random() % numerodomande)+ 1 is going to cause all sort of other problems due to it's position in the checking loop. Try one of these suggestions:

  • just return when you come across a dupe. No number gets added to the array...

  • set a BOOL test inside the loop, deal with it outside:

    BOOL repeatedValue = NO;
    for (int j=0; j<[self.oldquest count]; j++){
        if (randomValue==[self.oldquest[j] intValue]) {
            repeatedValue = YES;
            break;
        }
    }
    if (repeatedValue){
        NSLog (@"value repeated");
        [self addRandom];  
        //recursive call to this code block, 
        //assuming it is a method called `addRandom`
    }
    
  • Try a compact version of the last suggestion (similar to Odrakir's solution) - I've enclosed it in an addRandom method so you can see how to call it recursively.

      - (void) addRandom {
        int numberofquest = 5;
        int randomValue =  (arc4random() % numberofquest)+ 1;
        NSNumber* boxedValue = [NSNumber numberWithInt:randomValue];
        if ([self.oldquest indexOfObject:boxedValue]==NSNotFound) {
            [self.oldquest addObject: boxedValue];
        } else {
            [self addRandom];
        }
    }
    

    (If you do loop until you find a unique number you will have to watch out, as your total set of numbers is finitely limited to numberofquest, so when you have a full set you may end up with an infinite loop.)

  • Instead of using NSMutableArray, you could use MutableOrderedSet instead - it's an ordered collection of unique objects, so will not add an object twice.

in @interface

@property (nonatomic, strong) NSMutableOrderedSet setOfRandoms;

in @implementation

int randomValue =  (arc4random() % numberofquest)+ 1;
NSNumber randomValueBoxed = [NSNumber numberWithInt:randomValue];
[setOfRandoms addObject:randomValueBoxed];

update 3

The previous hints assumed it was the list of randoms you were interested in. Here is a complete solution to return a new unique random int in a self-contained method.

You need to set up 2 properties in your @interface and initialise them somewhere:

@property (nonatomic, assign) int maxRand;
    //stores the highest allowed random number

@property (nonatomic, strong) NSMutableArray* oldRands;
    //stores the previously generated numbers

uniqueRandom returns a unique random number between 1 and self.maxRand each time. If all allowable numbers have been returned it returns 0.

- (int) uniqueRandom {
    int result = 0;
    if ([self.oldRands count] != self.maxRand) {
        int randomValue =  (arc4random() %  self.maxRand )+ 1;
        NSNumber* boxedValue = [NSNumber numberWithInt:randomValue];
        if ([self.oldRands indexOfObject:boxedValue]==NSNotFound) {
            [self.oldRands addObject: boxedValue];
            result = randomValue;
        } else {
             result = [self uniqueRandom];
        }
    }
    return result;
}

You should consider that it doesn't make sense to change self.maxRand once it is initialised unless you also reset self.oldRands. So you might want to use a const or #define instead, and/or tie it in to your self.oldRands initialiser.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top