PDA

View Full Version : objective-c Cocoa: randomize a list




dj.mooky
Feb 26, 2008, 08:37 AM
So, I'm admittedly quite new to cocoa, and only half-way familiar with C to begin with, but I managed last night(till 4a.m.) to get my double elimination brackets app working, for the most part. But I hit a major problem.


The part that is killing me is a randomizer button, for the players in the tourny.

I have 16 "nplayerX" NSTextField's, which are used for name input, and to hold temporarily, before randomizing the names into 16 "playerX" NSTextField's that the app will actually use.

So, I dreamt up a way to make this work, but at 4 a.m. I dont have to question that it isn't the best way.



So the app does compile with only 2 warnings, the lines:
[randomName setStringValue:NSSelectorFromString([NSString stringWithFormat:@"player", @"%i", (i + 1)])];
[orderedName setStringValue:NSSelectorFromString([NSString stringWithFormat:@"nplayer", @"%d", (d +1)])];

Need to come up with a way to take a random number (i), and turn it into a binding(?) for "player(i)" and the for (d yadda yadda) so d can sit at the end of "player(d)"

Any brilliant coders know of a much simpler way to do this, or randomize a list in general?

Any help or insight would be lovely

-dj.mooky

Looking at this, I think I was onto something with making an array of names, and just doing 16 different small segments to make i random, name pnames[i] = nplayer(1-16 in order) with a check statement to see if it's already taken, hmmm.... That does seem easier to my brain, but it is still broken from lack of sleep -- anyway.


code snippets:
*************************************
// TournyController.h


@interface TournyController : NSObject {


// Edit Names window fields
IBOutlet NSTextField *nplayer1;
IBOutlet NSTextField *nplayer2;
IBOutlet NSTextField *nplayer3;
IBOutlet NSTextField *nplayer4;
IBOutlet NSTextField *nplayer5;
IBOutlet NSTextField *nplayer6;
IBOutlet NSTextField *nplayer7;
IBOutlet NSTextField *nplayer8;
IBOutlet NSTextField *nplayer9;
IBOutlet NSTextField *nplayer10;
IBOutlet NSTextField *nplayer11;
IBOutlet NSTextField *nplayer12;
IBOutlet NSTextField *nplayer13;
IBOutlet NSTextField *nplayer14;
IBOutlet NSTextField *nplayer15;
IBOutlet NSTextField *nplayer16;

// for the randomizer
IBOutlet NSTextField *randomName; // represents playerX
IBOutlet NSTextField *orderedName; // represents nplayerX
// PlayerName *pnames[15]; // failed attempt, left it in for later attempts maybe
bool placecounter[15]; // mark if that name has been used already

// Main window fields
IBOutlet NSTextField *player1;
IBOutlet NSTextField *player2;
IBOutlet NSTextField *player3;
IBOutlet NSTextField *player4;
IBOutlet NSTextField *player5;
IBOutlet NSTextField *player6;
IBOutlet NSTextField *player7;
IBOutlet NSTextField *player8;
IBOutlet NSTextField *player9;
IBOutlet NSTextField *player10;
IBOutlet NSTextField *player11;
IBOutlet NSTextField *player12;
IBOutlet NSTextField *player13;
IBOutlet NSTextField *player14;
IBOutlet NSTextField *player15;
IBOutlet NSTextField *player16;
}

- (IBAction)randomizeNames:(id) sender;


----------------------------------------
// tournycontroller.m

#import "TournyController.h"

@implementation TournyController

- (IBAction)randomizeNames:(id) sender {
// randomizes names while still in entering screen, before putting them in brackets

// seed the random number generator with the time, to get new randoms
srand(time(NULL));
int d=0;
int i=0;
for ( i=0; i<15; i++ ) {
placecounter[i] == false;
}

//force playerX to take nplayer values, so that nplayer values can be mixed up
[player1 setStringValue: [nplayer1 stringValue]];
[player2 setStringValue: [nplayer2 stringValue]];
[player3 setStringValue: [nplayer3 stringValue]];
[player4 setStringValue: [nplayer4 stringValue]];
[player5 setStringValue: [nplayer5 stringValue]];
[player6 setStringValue: [nplayer6 stringValue]];
[player7 setStringValue: [nplayer7 stringValue]];
[player8 setStringValue: [nplayer8 stringValue]];
[player9 setStringValue: [nplayer9 stringValue]];
[player10 setStringValue: [nplayer10 stringValue]];
[player11 setStringValue: [nplayer11 stringValue]];
[player12 setStringValue: [nplayer12 stringValue]];
[player13 setStringValue: [nplayer13 stringValue]];
[player14 setStringValue: [nplayer14 stringValue]];
[player15 setStringValue: [nplayer15 stringValue]];
[player16 setStringValue: [nplayer16 stringValue]];

// below is failed part of failed attempt.. I need to learn a little more about using arrays and classes it seems
// [pnames[0] setStringValue:[nplayer1 stringValue]];
// [pnames[1] setStringValue:[nplayer2 stringValue]];
// [pnames[2] setStringValue:[nplayer3 stringValue]];
// [pnames[3] setStringValue:[nplayer4 stringValue]];
// [pnames[4] setStringValue:[nplayer5 stringValue]];
// [pnames[5] setStringValue:[nplayer6 stringValue]];
// [pnames[6] setStringValue:[nplayer7 stringValue]];
// [pnames[7] setStringValue:[nplayer8 stringValue]];
// [pnames[8] setStringValue:[nplayer9 stringValue]];
// [pnames[9] setStringValue:[nplayer10 stringValue]];
// [pnames[10] setStringValue:[nplayer11 stringValue]];
// [pnames[11] setStringValue:[nplayer12 stringValue]];
// [pnames[12] setStringValue:[nplayer13 stringValue]];
// [pnames[13] setStringValue:[nplayer14 stringValue]];
// [pnames[14] setStringValue:[nplayer15 stringValue]];
// [pnames[15] setStringValue:[nplayer16 stringValue]];


// fix this ****! turn the string into something NSTextField can understand
i == (rand() % 15);
for (d=0; d<15; d++) {
while ( placecounter[i] == false ) {
i == (rand() % 15);
}
[randomName setStringValue:NSSelectorFromString([NSString stringWithFormat:@"player", @"%i", (i + 1)])];
[orderedName setStringValue:NSSelectorFromString([NSString stringWithFormat:@"nplayer", @"%d", (d +1)])];

[orderedName setStringValue:[randomName stringValue]];
placecounter[i] == true;
}

}



Eraserhead
Feb 26, 2008, 09:12 AM
random() is better than rand() for finding random numbers.

For a random list you will need to reduce the limit of the random numbers each time and add an appropriate number to cover the values already filled.

eharley
Feb 26, 2008, 05:09 PM
Check out the Fisher-Yates shuffle algorithm (http://en.wikipedia.org/wiki/Fisher-Yates_shuffle).

It does the shuffling in place by swapping elements in the array in a structured way. It's simple and more principled than an ad-hoc method.

dj.mooky
Feb 26, 2008, 10:52 PM
i tried to adapt the code you referenced in the shuffle algorithm. Since it came from a java code I cannot, to my knowledge(which is quite limited) use it in an objective-c cocoa framework(I really have no idea if you can mix the two).

Anyway, I have all the parts working except coming up with a randomized string to shift the strings from one array to another in random fashion. I'm about to write a small test app, to see if I can "control" the random() output at all, i'll report back on my findings, if I find a solution.

-dj.mooky

dj.mooky
Mar 3, 2008, 11:08 PM
Holy expletive batman!

OK! Well thank you eharley for pointing out that algorithm to me, because it is what eventually(and the same thing done in C++ code..) gave me the idea for a solution to my insanity!

here it is, just the randomize function -- I hope someone else can find this method useful, and that I caused and pulled several pre-mature gray hairs out for more than just one reason :)

- (IBAction)randomizeNames:(id) sender {
// randomizes names while still in entering screen, before putting them in brackets

int i=0;
NSMutableArray *pnames=[[NSMutableArray alloc] init];

// stores all nplayer names to an array pnames
[pnames addObject:[nplayer1 stringValue]];
[pnames addObject:[nplayer2 stringValue]];
[pnames addObject:[nplayer3 stringValue]];
[pnames addObject:[nplayer4 stringValue]];
[pnames addObject:[nplayer5 stringValue]];
[pnames addObject:[nplayer6 stringValue]];
[pnames addObject:[nplayer7 stringValue]];
[pnames addObject:[nplayer8 stringValue]];
[pnames addObject:[nplayer9 stringValue]];
[pnames addObject:[nplayer10 stringValue]];
[pnames addObject:[nplayer11 stringValue]];
[pnames addObject:[nplayer12 stringValue]];
[pnames addObject:[nplayer13 stringValue]];
[pnames addObject:[nplayer14 stringValue]];
[pnames addObject:[nplayer15 stringValue]];
[pnames addObject:[nplayer16 stringValue]];



// shuffle the ba-jebus out of the list by taking
// this is inside another for(40 times) loop to make sure it shuffles well... initial tests did not move slot 1 around alot for some reason
int r;
for (r=0; r<40; r++){
for (i=0; i<[pnames count]; i++) {
int d= i + (random() %([pnames count] -i));
NSString *temp=[pnames objectAtIndex:d];
//[temp setStringValue:[[pnames objectAtIndex:i] stringValue]];

[pnames removeObjectAtIndex:d];
[pnames addObject:temp];

// [pnames objectAtIndex:d] ==[pnames objectAtIndex:i]; // how do you do this correctly?
// [pnames objectAtIndex:i] ==temp;

// throws it out to console so I can make sure it's working right, will remove for release purposes
NSLog( @"made it past while() loop and - random number r: %d, pname[%d]: %@, pname[%d] %@.", d, i, [pnames objectAtIndex:i], d, [pnames objectAtIndex:d]);
}
}


[nplayer1 setStringValue:[pnames objectAtIndex:0]];
[nplayer2 setStringValue:[pnames objectAtIndex:1]];
[nplayer3 setStringValue:[pnames objectAtIndex:2]];
[nplayer4 setStringValue:[pnames objectAtIndex:3]];
[nplayer5 setStringValue:[pnames objectAtIndex:4]];
[nplayer6 setStringValue:[pnames objectAtIndex:5]];
[nplayer7 setStringValue:[pnames objectAtIndex:6]];
[nplayer8 setStringValue:[pnames objectAtIndex:7]];
[nplayer9 setStringValue:[pnames objectAtIndex:8]];
[nplayer10 setStringValue:[pnames objectAtIndex:9]];
[nplayer11 setStringValue:[pnames objectAtIndex:10]];
[nplayer12 setStringValue:[pnames objectAtIndex:11]];
[nplayer13 setStringValue:[pnames objectAtIndex:12]];
[nplayer14 setStringValue:[pnames objectAtIndex:13]];
[nplayer15 setStringValue:[pnames objectAtIndex:14]];
[nplayer16 setStringValue:[pnames objectAtIndex:15]];

}

iSee
Mar 4, 2008, 09:14 AM
Check out the Fisher-Yates shuffle algorithm (http://en.wikipedia.org/wiki/Fisher-Yates_shuffle).

It does the shuffling in place by swapping elements in the array in a structured way. It's simple and more principled than an ad-hoc method.

Er, sorry for being off topic but I can't believe Fisher and Yates get to put their names on this. I also "invented" this algorithm--in the 7th grade. It's not that I was particularly smart--it's just a fairly obvious solution (hah, this was on an 8K PET in BASIC, so it was worth it to think up an efficient method).

Regarding the OP's most recent code:
Your calculation of d is off. You should be picking a random number from 0 up-to-but-not-including [pnames count]-i. So you just need to remove the "i+" part:
int d= random() % ([pnames count] -i);
Also, you don't need to shuffle 40 times. If random() was a perfect random number generator then once will be fine. Of course it's not. Running though the shuffle a few times might help hide the bias in your method of generating random numbers, but 40 is pointless. (You might have been doing it before because of the bug with d-once you fix that, I think you'll find you don't need to shuffle repeatedly.

eharley
Mar 5, 2008, 07:02 AM
Er, sorry for being off topic but I can't believe Fisher and Yates get to put their names on this. I also "invented" this algorithm--in the 7th grade. It's not that I was particularly smart--it's just a fairly obvious solution (hah, this was on an 8K PET in BASIC, so it was worth it to think up an efficient method).


It's one thing to suggest the algorithm, it's another to prove that it actually results in all configurations being equally likely to come out the other end.