I have an array with a 14 strings. I want to display each of these 14 strings to the user without duplicates. The closest I got was creating an array of integers and shuffling their values, and then reading from the array of strings using one of the numbers from the int array as the index:
//appDelegate.randomRiddles is an array of integers that has integer values randomly
appDelegate.randomRiddlesCounter++;
NSNumber *index=[appDelegate.randomRiddles objectAtIndex:appDelegate.randomRiddlesCounter];
int i = [index intValue];
while(i>[appDelegate.currentRiddlesContent count]){
appDelegate.randomRiddlesCounter++;
index=[appDelegate.randomRiddles objectAtIndex:appDelegate.randomRiddlesCounter];
i = [index intValue];
}
hintText.text = [[appDelegate.currentRiddlesContent objectAtIndex:i] objectForKey:@"hint"];
questionText.text = [[appDelegate.currentRiddlesContent objectAtIndex:i] objectForKey:@"question"];
But my way is causing crashing and duplicates. Oh and each time I read a value from the strings array, that string is removed from the array making its count decrease by 1. So that complicates this a little bit.
Get the elements in your array like this:
int position = arc4random() % ([myArray count]);
This way even though count decreases by one, that is ok, as you will still get a valid next position value till there aren't any more posible values.
By "without duplicates" I assume you mean that you want to use each string in the array once before you use the same string again, not that you want to filter the array so it doesn't contain duplicate strings.
Here's a function that uses a Fisher-Yates shuffle:
/** @brief Takes an array and produces a shuffled array.
*
* The new array will contain retained references to
* the objects in the original array
*
* @param original The array containing the objects to shuffle.
* @return A new, autoreleased array with all of the objects of
* the original array but in a random order.
*/
NSArray *shuffledArrayFromArray(NSArray *original) {
NSMutableArray *shuffled = [NSMutableArray array];
NSUInteger count = [original count];
if (count > 0) {
[shuffled addObject:[original objectAtIndex:0]];
NSUInteger j;
for (NSUInteger i = 1; i < count; ++i) {
j = arc4random() % i; // simple but may have a modulo bias
[shuffled addObject:[shuffled objectAtIndex:j]];
[shuffled replaceObjectAtIndex:j
withObject:[original objectAtIndex:i]];
}
}
return shuffled; // still autoreleased
}
If you want to keep the relationship between the riddles, hints, and questions then I'd recommend using a NSDictionary to store each set of related strings rather than storing them in separate arrays.
This task is very easy using an NSMutableArray
. In order to do this, simply remove a random element from the array, display it to the user.
Declare a mutable array as an instance variable
NSMutableArray * questions;
When the app launches, populate with values from myArray
questions = [[NSMutableArray alloc] initWithArray:myArray]];
Then, to get a random element from the array and remove it, do this:
int randomIndex = (arc4random() % [questions count]);
NSDictionary * anObj = [[[questions objectAtIndex:randomIndex] retain] autorelease];
[questions removeObjectAtIndex:randomIndex];
// do something with element
hintText.text = [anObj objectForKey:@"hint"];
questionText.text = [anObj objectForKey:@"question"];
No need to type that much. To shuffle an array, you just sort it with random comparator:
#include <stdlib.h>
NSInteger shuffleCmp(id a, id b, void* c)
{
return (arc4random() & 1) ? NSOrderedAscending : NSOrderedDescending;
}
NSArray* shuffled = [original sortedArrayUsingFunction:shuffleCmp context:0];
You could copy the array into an NSMutableArray and shuffle that. A simple demonstration of how to shuffle an array:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Original array, here initialised with 1..9
NSArray *arr = [NSArray arrayWithObjects:
[NSNumber numberWithInt: 1],
[NSNumber numberWithInt: 2],
[NSNumber numberWithInt: 3],
[NSNumber numberWithInt: 4],
[NSNumber numberWithInt: 5],
[NSNumber numberWithInt: 6],
[NSNumber numberWithInt: 7],
[NSNumber numberWithInt: 8],
[NSNumber numberWithInt: 9],
nil];
// Array that will be shuffled
NSMutableArray *shuffled = [NSMutableArray arrayWithArray: arr];
// Shuffle array
for (NSUInteger i = shuffled.count - 1; i > 0; i--)
{
NSUInteger index = rand() % i;
NSNumber *temp = [shuffled objectAtIndex: index];
[shuffled removeObjectAtIndex: index];
NSNumber *top = [shuffled lastObject];
[shuffled removeLastObject];
[shuffled insertObject: top atIndex: index];
[shuffled addObject: temp];
}
// Display shuffled array
for (NSNumber *num in shuffled)
{
NSLog(@"%@", num);
}
[pool drain];
return 0;
}
Note that all arrays and numbers here are autoreleased, but in your code you might have to take care of memory management.
If you don't have to keep the elements in the array, you can simplify that (see Oscar Gomez' answer too):
NSUInteger index = rand() % shuffled.count;
NSLog(@"%@", [shuffled objectAtIndex: index]);
[shuffled removeObjectAtIndex: index];
At the end, shuffled will be empty. You will have to change the loop conditions too:
for (NSUInteger i = 0; i < shuffled.count; i++)