Reading random values from an array

2019-02-21 21:42发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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"];


回答4:

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];


回答5:

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++)