Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

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;
    }
}
 
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.
 
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.
 
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.
 
  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 :)
 
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.
 
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.
 
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:
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.
 
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];
 
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;
        }
    }
}
 
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.
 
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?
 
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?
 
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.
 
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.
 
@ 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, 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
 
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?
 
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:
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 ++)
 
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.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.