Sélection d'un objet aléatoire dans un NSArray

83

Disons que j'ai un tableau avec des objets, 1, 2, 3 et 4. Comment choisirais-je un objet aléatoire dans ce tableau?

Joshua
la source
Toutes les réponses ici sont correctes, mais pour une solution plus à jour, consultez ma réponse ici . Il utilise la arc4random_uniformméthode pour éviter les biais modulo.
Adam
pas une réponse à cette question, mais un point intéressant - d'autres collections Foundation (NSSet NSHashTable) ont des méthodes "anyObject" qui lisent un objet arbitraire (aléatoire) à partir du Set / HashTable. On pourrait implémenter cette méthode dans une extension de NSArray, en suivant les suggestions ci-dessous.
Motti Shneor

Réponses:

192

La réponse de @ Darryl est correcte, mais pourrait nécessiter quelques ajustements mineurs:

NSUInteger randomIndex = arc4random() % theArray.count;

Modifications:

  • L'utilisation de arc4random()over rand()and random()est plus simple car elle ne nécessite pas d'amorçage (appel srand()ou srandom()).
  • L' opérateur modulo ( %) rend l'instruction globale plus courte, tout en la rendant sémantiquement plus claire.
Dave DeLong
la source
27
De la page de manuel arc4random: arc4random_uniform () est recommandé par rapport aux constructions comme «arc4random ()% upper_bound» car il évite le «biais modulo» lorsque la borne supérieure n'est pas une puissance de deux.
Max Yankov
@collibhoy non, parce que 0 % 4 = 0, 1 % 4 = 1, 2 % 4 = 2, 3 % 4 = 3, 4 % 4 = 0, 5 % 4 = 1... Si vous modulo par n , votre plus grand résultat ne sera jamais supérieure à n-1 .
Dave DeLong
1
@DaveDeLong Selon le code source, countest une propriété:@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration> @property (readonly) NSUInteger count;
mikeho
17

C'est la solution la plus simple que je pourrais trouver:

id object = array.count == 0 ? nil : array[arc4random_uniform(array.count)];

Il est nécessaire de vérifier countcar un non- nilmais vide NSArrayretournera 0pour countet arc4random_uniform(0)retourne 0. Donc, sans le chèque, vous sortirez des limites du tableau.

Cette solution est tentante mais est erronée car elle provoquera un crash avec un tableau vide:

id object = array[arc4random_uniform(array.count)];

Pour référence, voici la documentation :

u_int32_t
arc4random_uniform(u_int32_t upper_bound);

arc4random_uniform() will return a uniformly distributed random number less than upper_bound.

La page de manuel ne mentionne pas que arc4random_uniformrenvoie 0quand 0est passé comme upper_bound.

Aussi, arc4random_uniformest défini dans <stdlib.h>, mais l'ajout #importn'était pas nécessaire dans mon programme de test iOS.

funroll
la source
11

Peut-être quelque chose du genre:

NSUInteger randomIndex = (NSUInteger)floor(random()/RAND_MAX * [theArray count]);

N'oubliez pas d'initialiser le générateur de nombres aléatoires (srandomdev (), par exemple).

REMARQUE: j'ai mis à jour pour utiliser -count au lieu de la syntaxe dot, conformément à la réponse ci-dessous.

Darryl H. Thomas
la source
9
@interface NSArray<ObjectType>  (Random)
- (nullable ObjectType)randomObject;
@end

@implementation NSArray (Random)

- (nullable id)randomObject
{
    id randomObject = [self count] ? self[arc4random_uniform((u_int32_t)[self count])] : nil;
    return randomObject;
}

@end

Edit: mis à jour pour Xcode 7. Generics, nullability

Alexandre Belyavskiy
la source
1

Générez un nombre aléatoire et utilisez-le comme index. Exemple:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSArray *array = [NSArray arrayWithObjects: @"one", @"two", @"three", @"four", nil];
        NSUInteger randomNumber;
        int fd = open("/dev/random", O_RDONLY);
        if (fd != -1) {
            read(fd, &randomNumber, sizeof(randomNumber));
            close(fd);
        } else {
            fprintf(stderr, "Unable to open /dev/random: %s\n", strerror(errno));
            return -1;
        }
        double scaledRandomNumber = ((double)randomNumber)/NSUIntegerMax * [array count];
        NSUInteger randomIndex = (NSUInteger)floor(scaledRandomNumber);
        NSLog(@"random element: %@", [array objectAtIndex: randomIndex]);
    }
    return 0;
}

la source
@Joshua si vous voulez un peu plus de détails, vous pouvez utiliser SecRandomCopyBytes()pour obtenir des nombres aléatoires cryptographiquement utiles, sur l'iPhone de toute façon. Sur Mac, vous avez un accès direct à / dev / random.
Je pense que le point principal de la question est de montrer comment choisir un élément aléatoire dans le tableau, et cette réponse ne donne pas vraiment les meilleures informations.
beakr
J'aime bien cette blague, mais je l'ai rejetée pour aider ceux qui ne comprennent pas.
Stig Brautaset
0
 srand([[NSDate date]  timeIntervalSince1970]);

 int inx =rand()%[array count];

inx est le nombre aléatoire.

où srand () peut être n'importe où dans le programme avant la fonction de sélection aléatoire.

Apprenant pathétique
la source
0
ObjectType *objectVarName = [array objectAtIndex:arc4random_uniform((int)(array.count - 1))];

si vous voulez convertir cela en un int, voici la solution pour cela (utile lorsque vous avez besoin d'un int aléatoire à partir d'un tableau de nombres non séquentiels, dans le cas de la randomisation d'un appel enum, etc.)

int intVarName = (int)[(NSNumber *)[array objectAtIndex:arc4random_uniform((int)(array.count - 1))] integerValue];
jungledev
la source
0

Dans Swift 4:

let array = ["one","two","three","four"]
let randomNumber = arc4random_uniform(UInt32(array.count))

array[Int(randomNumber)]
Garg Ankit
la source
1
Veuillez consulter Comment écrire une bonne réponse . Les réponses de code uniquement sont déconseillées car elles n'expliquent pas comment elles résolvent le problème dans la question. Vous devriez mettre à jour votre réponse pour expliquer ce que cela fait et comment cela améliore les nombreuses réponses que cette question de 7 ans a déjà
FluffyKitten