TheInterestedOne
TheInterestedOne

Reputation: 768

Array empty in a Non repeating algorithm for random numbers in Xcode with arc4random

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?

Upvotes: 1

Views: 1380

Answers (3)

Odrakir
Odrakir

Reputation: 4254

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

Upvotes: 2

foundry
foundry

Reputation: 31745

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.

Upvotes: 1

Ares
Ares

Reputation: 5903

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.

Upvotes: 1

Related Questions