iOS Random and non repeated text

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
Is it possible somehow to make this non repeated? Im reading on the internet about the Fisher Yates method and Im wondering if its any easy way to use it in this easy codes.

Fisher-yates shuffle method

Code:
- (void)shuffleArray:(NSMutableArray *)array {
    //fisher-yates shuffle
    for (int i = [array count] - 1; i > 0; i--) {
        int j = arc4random() % [array count];
        [array exchangeObjectAtIndex:i withObjectAtIndex:j];
    }
}
For example exchange the "% 6;" to some function?

Code:
-(IBAction)randomWords:(id)sender {
    int ran = arc4random() % 6;
    switch (ran) {
            case 0:
                textlabel.text = @"Cat";
                break;
            case 1:
                textlabel.text = @"Dog";
                break;
            case 2:
                textlabel.text = @"Fish";
                break;
            case 3:
                textlabel.text = @"Horse";
                break;
            case 4:
                textlabel.text = @"Bird";
                break;
            case 5:
                textlabel.text = @"f";
             break;    
            default:
                break;
    }
}
 

ArtOfWarfare

macrumors G3
Nov 26, 2007
8,579
4,019
I'm confused, what exactly are you looking to do?

Are you trying to shuffle a bunch of objects?

Just call rand() repeatedly and assign the generated value to each object. Then sort the objects in ascending order by that random value. Seed random when your app launches with the current time, so that the "random" values aren't the same each time your app is closed and started again.
 

dejo

Moderator
Staff member
Sep 2, 2004
15,981
447
The Centennial State
Wirelessly posted (Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3)

Or use arc4random(). It's self-seeding.
 

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
I'm confused, what exactly are you looking to do?

Are you trying to shuffle a bunch of objects?

Just call rand() repeatedly and assign the generated value to each object. Then sort the objects in ascending order by that random value. Seed random when your app launches with the current time, so that the "random" values aren't the same each time your app is closed and started again.
Yes I'm trying to shuffle a bunch of objects/words.
I wrote down the words in a textfile and trying now to change the Label randomly to one of the words of the textfile, how should the code look like?

So far I got this:

Code:
-(IBAction)random:(id)sender {
    int index = (arc4random() % 6) - 1;
    wordslabel.text = NSString *path = [[NSBundle mainBundle] pathForResource:@"words" ofType:@"txt"];;
}
If anyone could help me with some guidance.
 

ianray

macrumors 6502
Jun 22, 2010
452
0
@
  1. Get words from file into array
  2. Choose array entry index randomly
  3. ???
  4. Profit

Sorry for trying to be funny, but this problem is straightforward when you break it down in to pieces. Good luck :)
 

dejo

Moderator
Staff member
Sep 2, 2004
15,981
447
The Centennial State
I think some of you are forgetting that the random choices cannot be repeating. A couple of solutions come to mind: 1) remove the word from the array when chosen, 2) store the chosen indexes and check against them when choosing a new one.
 

PhoneyDeveloper

macrumors 68040
Sep 2, 2008
3,114
93
After you have shuffled the array with the code you show then pick the first value in the array and remove it. Then repeat this next time. It won't repeat.

This is just like a deck of cards. Shuffle the cards so they are in random order. Then take one card and remove it. Repeat this until there are no more cards. Then do it again.
 

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
I think some of you are forgetting that the random choices cannot be repeating. A couple of solutions come to mind: 1) remove the word from the array when chosen, 2) store the chosen indexes and check against them when choosing a new one.
Method 1.
Do you mean something like this?

.m file
Code:
@interface ViewController : UIViewController{
    IBOutlet UILabel* textlabel;
NSMutableArray* randomArray;
}
@property (nonatomic, retain)			NSMutableArray* randomArray;
.h file
Code:
-(IBAction)random:(id)sender {
    int index = (arc4random() % 6) - 1;
    textlabel.text = [[NSBundle mainBundle] pathForResource:@"words" ofType:@"txt"];
    self.textlabel = [NSArray arrayWithContentsOfFile: @"/Users/jimmylind91/Documents/xCode/test3/test3"];
    [randomArray removeLastObject];
    if ([randomArray count] ==0)
		return nil;
    
}
 
Last edited:

ArtOfWarfare

macrumors G3
Nov 26, 2007
8,579
4,019
Here's the steps you need to do:

1 - Put the contents of the text file into an NSString. (You're currently putting the contents into a label, which, while it works, violates MVC.)
2 - Break the NSString into an NSArray of substring NSStrings. (Look through the documentation on NSString; there's a handy method that'll do this for you.)
3 - Make a mutable copy of the NSArray.
4 - [array objectAtIndex: (rand() % [array count])]; will give you a random object from your array. Or at least it should. If you don't understand why, looking into the modulus operator (%) would be a good idea.
5 - Do whatever you need to with the random object from the list.
6 - NSMutableArray has a handy method that'll remove an object from it for you. Check the documentation.
7 - Repeat steps 3 through 6 as many times as you need to.

Oh, and as a zeroth step, you should also seed random when your app starts up, possibly with the current time.
 

mobilehaathi

macrumors G3
Aug 19, 2008
9,351
6,219
The Anthropocene
I think the Fisher-Yates shuffle is about as good as you'll get in terms of efficiency. What about it don't you like? Given a good random number generator each possible permutation will happen with 1/n! probability. The only problem I see is that I believe you should have

Code:
int j = arc4random() % i+1;
instead of

Code:
int j = arc4random() % [array count];
 

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
I think the Fisher-Yates shuffle is about as good as you'll get in terms of efficiency. What about it don't you like? Given a good random number generator each possible permutation will happen with 1/n! probability. The only problem I see is that I believe you should have

Code:
int j = arc4random() % i+1;
instead of

Code:
int j = arc4random() % [array count];
How do I use the Fisher-Yates in the IBAction function?
Something like this?

Code:
- (void)shuffleArray:(NSMutableArray *)array {
    //fisher-yates shuffle
    for (int i = [array count] - 1; i > 0; i--) {
        int j = arc4random() % i+1;
        [array exchangeObjectAtIndex:i withObjectAtIndex:j];
    }
}
Code:
-(IBAction)randomWords:(id)sender {
    for (int i = [array count] - 1; i > 0; i--) {
    int j = arc4random() % i+1;
    switch (j) {
        case 0:
            textlabel.text = @"Cat";
            break;
        case 1:
            textlabel.text = @"Dog";
            break;
        case 2:
            textlabel.text = @"Fish";
            break;
        case 3:
            textlabel.text = @"Horse";
            break;
        case 4:
            textlabel.text = @"Bird";
            break;
        case 5:
            textlabel.text = @"f";
            break;    
        default:
            break;
        }
    }
}
 

chown33

Moderator
Staff member
Aug 9, 2009
8,562
4,635
inter-prandial
How do I use the Fisher-Yates in the IBAction function?
Something like this?
Nothing like that.

You're not breaking it down and thinking it through. Forget the code for a minute. Just think it through.

You have an array of strings that's already shuffled. That means its items are in random order. What do you have to do to get one random item from the array?

The answer is simple: take the first item. That's all. Why? Because the items are already in random (shuffled) order.

Next, to prevent repeats, what do you have to do?

Again, the answer is simple: remove the first item (the item you took). Think about why this works.

Finally, since the item is a string, what do you have to do to set a label's text to that item?

The answer here is also simple. You've already shown code for it. But in your code, you're assigning a string constant (like @"Dog") instead of the item from the array.

So break the problem down into simple solvable steps, then solve each step, then write the code for each solved step. Notice that this is entirely about logical thinking and simple solutions, except for the last part, which is writing the code. The code you posted makes no logical sense, does way more than necessary (randomly picking items from a shuffled array), and is simply nonsense when it comes to assigning texts to labels. Fisher-Yates won't help you if your code is illogical nonsense. Think first; code later.

I'm assuming the array holds strings that are intended to be used as actual label text. If the array holds numbers or other things, then you should rethink what to change, or rethink what's in the file you read from disk.
 

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
Nothing like that.

You're not breaking it down and thinking it through. Forget the code for a minute. Just think it through.

You have an array of strings that's already shuffled. That means its items are in random order. What do you have to do to get one random item from the array?

The answer is simple: take the first item. That's all. Why? Because the items are already in random (shuffled) order.

Next, to prevent repeats, what do you have to do?

Again, the answer is simple: remove the first item (the item you took). Think about why this works.

Finally, since the item is a string, what do you have to do to set a label's text to that item?

The answer here is also simple. You've already shown code for it. But in your code, you're assigning a string constant (like @"Dog") instead of the item from the array.

So break the problem down into simple solvable steps, then solve each step, then write the code for each solved step. Notice that this is entirely about logical thinking and simple solutions, except for the last part, which is writing the code. The code you posted makes no logical sense, does way more than necessary (randomly picking items from a shuffled array), and is simply nonsense when it comes to assigning texts to labels. Fisher-Yates won't help you if your code is illogical nonsense. Think first; code later.

I'm assuming the array holds strings that are intended to be used as actual label text. If the array holds numbers or other things, then you should rethink what to change, or rethink what's in the file you read from disk.
Okay let me know if I got this right, first of all I need to break down the problem in different steps, just like ianray said before.

  1. Get words from file into array
  2. Choose array entry index randomly
  3. ???
  4. Profit

Sorry for trying to be funny, but this problem is straightforward when you break it down in to pieces. Good luck :)
If I understood it all this is what I need to accomplish?
  • Make the words from the file get in the array/index
  • Make the words from the array/index appear randomly
  • Remove words After the words appears from the array/index
  • If the array/index has come down to 0 words make it return nil?
 

mobilehaathi

macrumors G3
Aug 19, 2008
9,351
6,219
The Anthropocene
Okay let me know if I got this right, first of all I need to break down the problem in different steps, just like ianray said before.



If I understood it all this is what I need to accomplish?
  • Make the words from the file get in the array/index
  • Make the words from the array/index appear randomly
  • Remove words After the words appears from the array/index
  • If the array/index has come down to 0 words make it return nil?
Well, once you've exhausted your shuffled array of label names, what would you like to happen?
 

MattInOz

macrumors 68030
Jan 19, 2006
2,761
0
Sydney
Well, once you've exhausted your shuffled array of label names, what would you like to happen?
It seems pretty expensive to me to create an array of NSStrings just to trash them one by one if your going to need them again.

I wonder if you could do the same with say a NSMutableIndexSet. Pick an Index at random out of the remaining ones then delete it from the set. Sure you'd need to reboot the NSIndexSet once it was empty but seems a lot cheaper than a new array.
 

mobilehaathi

macrumors G3
Aug 19, 2008
9,351
6,219
The Anthropocene
It seems pretty expensive to me to create an array of NSStrings just to trash them one by one if your going to need them again.

I wonder if you could do the same with say a NSMutableIndexSet. Pick an Index at random out of the remaining ones then delete it from the set. Sure you'd need to reboot the NSIndexSet once it was empty but seems a lot cheaper than a new array.
Actually, I'm not really sure what precisely the OP wants to accomplish. If he is just trashing them as he uses them, then your solution seems better. If the OP wants to keep the set/array of strings and permute again after running through the first random permutation, then he shouldn't trash them at all and the random permutation algorithm he has will work well to generate another random permutation.
 

mobilehaathi

macrumors G3
Aug 19, 2008
9,351
6,219
The Anthropocene
@ MattInOz, those two strategies are exactly the same. Prove me wrong.
I'm not familiar with the specific implementation of NSMutableIndexSet, but it could potentially be marginally less expensive to pick a random element and delete it versus shuffling an NSMutableArray then deleting the elements in order.

However, I do agree that the end result is the same.
 

MattInOz

macrumors 68030
Jan 19, 2006
2,761
0
Sydney
@ MattInOz, those two strategies are exactly the same. Prove me wrong.
Well to me the big fundamental difference is that one is entirely independent of the data. It just works on making sure you have a stream of indexes that don't repeat till all are exhausted. You could apply it to switch/case method if the data is small or in another case that the index would be enough, like say a deck of cards.
Not sure how I really prove that to you. Other than maybe a test example.:D

Note: All code is ARC. Taking xCode Command Line Application Templet
Example will Log out 40 Animal Names without repeating one of the set until all have been used.

main.m file
Code:
#import <Foundation/Foundation.h>
#import "NSMutableIndexSet+oneTime.h"

int main (int argc, const char * argv[])
{

    @autoreleasepool {
        
        // insert code here...
        NSLog(@"Hello, World!");
        NSRange indexRange = NSMakeRange(0, 6);
        NSMutableIndexSet *newIndexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:indexRange];
        for (int i=0; i<40; i++) 
        {
            NSUInteger x = [newIndexSet oneTimeRandomIndex];
            if (x == NSNotFound) 
            {
                                       // NSLog(@"newIndexSet needs to be refilled");
                [newIndexSet addIndexesInRange:indexRange];
                x = [newIndexSet oneTimeRandomIndex];
                
            }
            switch (x) {
                case 0:
                    NSLog(@"Cat");
                    break;
                case 1:
                    NSLog(@"Dog");
                    break;
                case 2:
                    NSLog(@"Fish");
                    break;
                case 3:
                    NSLog(@"Horse");
                    break;
                case 4:
                    NSLog(@"Bird");
                    break;
                case 5:
                    NSLog(@"Wombat");
                    break;    
                default:
                    break;
            }
            
        }

         NSLog(@"See Ya Later, World!");
    }
    return 0;
}
Oh yeah important bit a category on NSMutableIndexSet. Although you could do it without this but now that I've thought about it I want to be able to reuse.
NSMutableIndexSet+oneTime.h
Code:
#import <Foundation/Foundation.h>

@interface NSMutableIndexSet (oneTime)
-(NSUInteger)oneTimeRandomIndex;
//returns one index from set at random, removing the index from the set so that it won't be returned again.
//As the index set is mutable indexes maybe re-added to the set to become re-eligsble for return by this method.
//Will return NSNotFound if NSMutableIndexSet is empty.
@end
NSMutableIndexSet+oneTime.m
Code:
#import "NSMutableIndexSet+oneTime.h"

@implementation NSMutableIndexSet (oneTime)
-(NSUInteger)oneTimeRandomIndex
{
    if (self.count < 1) 
    {
        return NSNotFound;
    }
    NSUInteger x = [self lastIndex];
    x = arc4random() % x;
    x = [self indexLessThanOrEqualToIndex:x];
    if (x == NSNotFound) 
    {
        x = [self indexLessThanOrEqualToIndex:x];
    }
    [self removeIndex:x];
    return x; 
}

@end
 

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
Well, once you've exhausted your shuffled array of label names, what would you like to happen?
I guess I need to make it start over from the beginning?

MattInOz, That was some advanced coding for me, care for a little more explaining?
 

mobilehaathi

macrumors G3
Aug 19, 2008
9,351
6,219
The Anthropocene
I guess I need to make it start over from the beginning?
Ok I normally don't do this, but you should probably have something like this.

Code:
- (void)shuffleArray:(NSMutableArray *)array {
    //fisher-yates shuffle
    for (int i = [array count] - 1; i > 0; i--) {
        int j = arc4random() % i+1;
        [array exchangeObjectAtIndex:i withObjectAtIndex:j];
    }
}

-(IBAction)randomWords:(id)sender {
    
	textlabel.text = [array objectAtIndex:currentIndex];
	
	currentIndex++;
	
	if(currentIndex >= [array count])
	{
		[shuffleArray array];
		currentIndex = 0;
	}
           
}
array here is an NSMutableArray filled with your strings. You would shuffle the array once after you've read in your data (not shown above). You'll also want to keep an int (currentIndex above, which you should init to 0 when you read in your data) to track your index in the array. This doesn't trash your strings as you use them, but rather once you've reached the end of your shuffled array it resets your current index and shuffles the array again. Does this make sense? I fear that by giving you the code you might not understand what's going on.
 
Last edited:

Jimmyl1

macrumors newbie
Original poster
Jan 12, 2012
7
0
Ok I normally don't do this, but you should probably have something like this.

Code:
- (void)shuffleArray:(NSMutableArray *)array {
    //fisher-yates shuffle
    for (int i = [array count] - 1; i > 0; i--) {
        int j = arc4random() % i+1;
        [array exchangeObjectAtIndex:i withObjectAtIndex:j];
    }
}

-(IBAction)randomWords:(id)sender {
    
	textlabel.text = [array objectAtIndex:currentIndex];
	
	currentIndex++;
	
	if(currentIndex >= [array count])
	{
		[shuffleArray array];
		currentIndex = 0;
	}
           
}
array here is an NSMutableArray filled with your strings. You would shuffle the array once after you've read in your data (not shown above). You'll also want to keep an int (currentIndex above, which you should init to 0 when you read in your data) to track your index in the array. This doesn't trash your strings as you use them, but rather once you've reached the end of your shuffled array it resets your current index and shuffles the array again. Does this make sense? I fear that by giving you the code you might not understand what's going on.
By watching the codes and reading your explanation I start to get it but I wonder what
Code:
	currentIndex++;
does and means (the ++)
 

ArtOfWarfare

macrumors G3
Nov 26, 2007
8,579
4,019
By watching the codes and reading your explanation I start to get it but I wonder what
Code:
	currentIndex++;
does and means (the ++)
x++ is post increment.

It means add one to x (or whatever variable you have in place of x, in this case currentIndex.)

I suspect you haven't learned much C yet and would suggest that you do.