Saturday, September 3, 2011

Randomizing the questions!

I'd like to randomize the Question array, which needs some kind of a shuffle. This code from http://iphonedevelopment.blogspot.com/2008/10/shuffling-arrays.html looks promising, but unfortunately I don't know how to use it.
NSArray-Shuffle.h #import @interface NSArray(Shuffle) -(NSArray *)shuffledArray; @end NSArray-Shuffle.m #import "NSArray-Shuffle.h" @implementation NSArray(Shuffle) -(NSArray *)shuffledArray { NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self count]]; NSMutableArray *copy = [self mutableCopy]; while ([copy count] > 0) { int index = arc4random() % [copy count]; id objectToMove = [copy objectAtIndex:index]; [array addObject:objectToMove]; [copy removeObjectAtIndex:index]; } [copy release]; return array; } @end
An entry on SO sheds some insight: http://stackoverflow.com/questions/5659718/shuffling-an-array-in-objective-c
// NSMutableArray_Shuffling.h #if TARGET_OS_IPHONE #import #else #include #endif // This category enhances NSMutableArray by providing // methods to randomly shuffle the elements. @interface NSMutableArray (Shuffling) - (void)shuffle; @end // NSMutableArray_Shuffling.m #import "NSMutableArray_Shuffling.h" @implementation NSMutableArray (Shuffling) - (void)shuffle { static BOOL seeded = NO; if(!seeded) { seeded = YES; srandom(time(NULL)); } NSUInteger count = [self count]; for (NSUInteger i = 0; i < count; ++i) { // Select a random element between i and end of array to swap with. int nElements = count - i; int n = (random() % nElements) + i; [self exchangeObjectAtIndex:i withObjectAtIndex:n]; } }
It looks like this sort of adds a capability to an object without actually subclassing it. So, if you pull this into your program, you should be able to call "shuffle" - or "shuffledArray". But how does it now what array it's doing the shuffling on? Let's take a look at what categories are: http://macdevelopertips.com/objective-c/objective-c-categories.html
Categories an alternative to subclassing, Objective-C categories provide a means to add methods to a class. What’s intriguing, is that any methods that you add through a category become part of the class definition, so to speak. In other words, if you add a method to the NSString class, any instance, or subclass, of NSString will have access to that method. Defining a category is identical to defining the interface for a class, with one small exception: you add a category name inside a set of parenthesis after the interface declaration. The format is shown below: @interface ClassToAddMethodsTo (category) ...methods go here @end For example, below I’ve defined a category that adds a method to the NSString class. The method reverseString adds the capability to all NSString objects to reverse the characters in the string. @interface NSString (reverse) -(NSString *) reverseString; @end As with the @interface declaration, the @implementation section changes only in that the category name is added to the definition. Below is the implementation of the interface defined above. Notice how in both cases I added (reverse) , which is the category name I assigned. @implementation NSString (reverse) -(NSString *) reverseString { NSMutableString *reversedStr; int len = [self length]; // Auto released string reversedStr = [NSMutableString stringWithCapacity:len]; // Probably woefully inefficient... while (len > 0) [reversedStr appendString: [NSString stringWithFormat:@"%C", [self characterAtIndex:--len]]]; return reversedStr; } @end What follows is a short example to showing how one might use the above category. #import #import "NSString+Reverse.h" int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *str = [NSString stringWithString:@"Fubar"]; NSString *rev; NSLog(@"String: %@", str); rev = [str reverseString]; NSLog(@"Reversed: %@",rev); [pool drain]; return 0; }
Cool. So, all I really need to do is get the files into my project, then, and act as if they're part of the class. Probably the easiest way to handle this is to create an array of numbers equal to the size of the question hash, and then get the shuffled array from that. Here's where in the code it implements the increment of question:
/* call back method on pop of answer page */ - (void) viewWillAppear:(BOOL)animated{ // NSLog(@"view will appear was called"); // won't be animated on the first call if (animated) { appState.currentQuestionNumber++; [appState saveState]; }
Now - where in this project do we put the code to do this shuffle? One good candidate would be in the app delegate. It call the Database reader. it could just go into a loop to populate the mixed array, determined by the length of the question hash. Then, all it needs to do is shuffle the array, or get the new shuffled array. Ah, ok. In order to get the classification to work, you need to import the header of the object with the classification, in this case:
#import "NSArray-Shuffle.h"
Here's the code to implement it:
NSArray *keys = [appState.questionHash allKeys]; NSArray *shuffledKeys = [keys shuffledArray];
However, there is a problem. The problem is that who knows what the order of keys is in the first place? Shuffled keys is probably more random, though. How do we read from the array? Adapted from wiki:
for (id obj in shuffledKeys) { // do something with object NSLog(@"%@", obj); // Leave as an object }
Ok, and finally, we just need to go ahead and read from this randomized array sequentially, and then read from the question dictionary by key based on that. Here again is the code that makes the call to increment to the next question:
// won't be animated on the first call if (animated) { appState.currentQuestionNumber++; [appState saveState]; } currentQuestion = [appState currentQuestion];
So, if we look at the currentQuestion method in appState, here it is:
- (Question *) currentQuestion { // Convert int to unsigned number NSNumber *key = [NSNumber numberWithInt:currentQuestionNumber]; // retrieve the question from the hash Question * question = [questionHash objectForKey:key]; return question; }
So, the current question number should be more helpfully called "randomKeyArrayIndex" or something like that. Also, it would probably make more sense to put it into app state than the app delegate.
NSArray *keys = [appState.questionDict allKeys]; appState.shuffledQuestionDictKeyList = [keys shuffledArray];
Ok, now that that's done, it's just a question of modifying the "currentQuestion" method in app state to return the question based on the entry in the randomized array pointed to by the current (sequential) index:
- (Question *) currentQuestion { // Get the key from the shuffled array NSNumber *key = [shuffledQuestionDictKeyList objectAtIndex:currentShuffledArrayIndex]; // retrieve the question from the hash Question * question = [questionDict objectForKey:key]; return question; }
And run it...yes. We're finally seeing something different that the first usual entry in the list.

No comments:

Post a Comment